Laden...
Avatar #avatar-3263.gif
elTorito myCSharp.de - Member
Dynamics Nav - ERP System Manager - gelernter Anwendungsentwickler Niederrhein Dabei seit 28.07.2009 177 Beiträge
Benutzerbeschreibung

Forenbeiträge von elTorito Ingesamt 177 Beiträge

22.05.2015 - 14:14 Uhr

Hallo Abt,

brringt es mir denn etwas Cancellation bzw. OperationCanceledException abzufangen, wenn der abgesetzte Befehl kein "Cancelln" unterstützt?

OperationCanceledException würde (wird) auch erst geschmissen nachdem die eigentliche Exception kam, klar, catche ich die vor der allgemeinen Exception, bekomme ich OperationCancelled. Aber so oder so kann ich den "Verbindungsbefehl" nicht abbrechen, weil der Zyan Server das glaube ich nicht unterstützt. (Also so als wenn man mit dem Auto ohne Bremsen losrollen würde, und entweder rollt man aus, oder rollt gegen ein Baum, oder solange bis was passiert)

22.05.2015 - 13:21 Uhr

Hi,

Alex(yallie) war so freundlich

falls mal noch jemand das braucht:
👍

Hi Peter,

Use async methods on the client side only.  
Don't use Task types in your interfaces, they are not serializable anyway.  
Whenever you call a server method, wrap the call with Task.Run().  

Just as simple as that.

Here is an example:

  
// note that server component interface is synchronous  
public interface ISampleService  
{  
    string GeneratePassword(int length);  
}  
  
// and client code is asynchronous  
using (var conn = await Task.Run(() => new ZyanConnection(url, protocol)))  
{  
    var proxy = conn.CreateProxy<ISampleService>();  
  
    var password = await Task.Run(() => proxy.GeneratePassword(length));  
    Console.WriteLine("Server generated password: {0}", password);  
}  
  

The complete code for this example is located here:

>

Regards, Alex

22.05.2015 - 13:17 Uhr

Hab das jetzt so umgesetzt.

Wenn ButtoNCancel gedrückt, abbrechen, und beim Catchen der Exception


catch (Exception ex)
{
   if (cancellationToken.IsCancellationRequested)
   { 
     //Do Nothing 
   } else 
     infotext.text = ex.Message;
}

ich geh einfach mal nicht von Steptanz auf den Buttons aus, denn so könnte es passieren dass im moment der exception gerade kein cancellationtoken vorhanden ist... falls nun doch die Exception durchrutscht und im Infofenster landet, ist es auch nicht weiter Schlimm...

und eigentliches Ziel war dass die UI nicht einfriert beim Verbindungsversuch (hatte ich schon fast wieder vergessen 😉 )

Das habe ich nun durch Async/Await gelöst.


var someTasks = Task.Run(() =>
            {
                ConnectToServer(cancellationToken, userNameTextBox.Text, passwordTextBox.Text);
                CheckSqlConnectionExists();

            }, cancellationToken);

            try
            {
                await someTasks;
                LoginDLg = DialogResult.OK;
                this.Close();
            }

Danke für eure Antworten.

11.05.2015 - 08:31 Uhr

Die gleiche Methode Synchron, kommt kein Fehler.

Habe nun nochmal die Asynchrone getestet, die Methode wird korrekt ausgeführt, liefert im beispiel oben True/false je nachdem ob SQL Server vorhanden, aber eben "Ist als nicht serialisierbar" gekennzeichnet. Hmm...

08.05.2015 - 15:13 Uhr

Hast du denn das Zielframework auf mindestens Net 4.5 umgestellt?

Ja. Auf 4.5
Und die ZyanCommunication.dll 4.5 von 43825 (2.6?)

08.05.2015 - 13:58 Uhr

Hallo,

ich möchte meine Anwendung auf Asynchron umstellen, zum testen versuche ich folgendes Beispiel:


public async Task<bool> SqlConnectionExistsAsync()
{
  try
  {
    return await zyanProxy.SqlConnectionExistsAsync();
  }
  catch (Exception ex)
  {
    System.Diagnostics.Debug.WriteLine("ex " + ex.Message);
  }
 }

public interface ILogonServiceAsync : ILogonService
    {
        Task<bool> SqlConnectionExistsAsync();
    }

//Zyan program.cs:
catalog1.RegisterComponent<ILogonServiceAsync, LogonService>();

 public async Task<bool> SqlConnectionExistsAsync()
        {
            string connString = ConfigurationManager.ConnectionStrings["ExampleConnection"].ConnectionString;

            using (SqlConnection connection = new SqlConnection(connString))
            {
                try
                {
                    await connection.OpenAsync();
                    return true;
                }
                catch (SqlException ex)
                {
                    return false;
                }
            }
        }

Die Verbindung kommt zustande, der user loggt ein.

Der Debugger steigt mir anschließend aus im Zyan ConnectionErrorEventHandler. mit : > Fehlermeldung:

Der Typ "System.Threading.Tasks.Task`1[[System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]" in Assembly "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" ist nicht als serialisierbar gekennzeichnet.

Habe versucht mir das hier abzugucken:
http://zyan.codeplex.com/discussions/347146

Was mache ich falsch? Wie könnte ich es richtig machen?

Danke.

07.05.2015 - 17:41 Uhr

Das mit der Wartezeit ist doch grober Unfug.

Starte einfach die Anfrage und gut ist. Wenn die Anfrage läuft und der Benutzer haut auf Abbrechen, dann signalisiere das über das CancellationToken. Kommt die Anfrage zu einem Ergebnis dann wird einfach das CancellationToken geprüft und bei "Oh, abgebrochen" einfach nichts mehr machen.

Muss ich nochmal drüber nachdenken, aber klingt mal ganz danach als würde ich es wieder viel komplizierter machen als es muss...

07.05.2015 - 10:20 Uhr

Ich weiß nicht, wie der Service aussieht zu dem du dich verbinden möchtest. Aber sofern es von der Schnittstelle her nix zum Abbrechen gibt wird da auch nichts abgebrochen werden.

Hi,

okay, also wenn die Service Schnittstelle nichts bereitstellt zum Abbrechen, kann nicht abgebrochen werden, und der Prozess läuft durch. Ich kann anstellen was ich möchte, sobald der "Verbindungsbefehl" abgesetzt wurde, gibt es kein zurück...

Nun habe ich mal versucht das ganze zu "verpacken" :


private void OKButton_Click(object sender, EventArgs e)
{
ConnectToServiceAsync();
}
private void CancelButton_Click(object sender, EventArgs e)
{
if (cts != null)
{
System.Diagnostics.Debug.WriteLine("Cancel Button Clicked";);
cts.Cancel();
}
}


private async void ConnectToServiceAsync()
{
cts = new CancellationTokenSource();
var cancellationToken = cts.Token;

var someTask = Task.Run(() =>
{
System.Diagnostics.Debug.WriteLine("SomeTask is Running...";);
System.Threading.Thread.Sleep(2000);
if (cancellationToken.IsCancellationRequested)
{
System.Diagnostics.Debug.WriteLine("cancellationToken.IsCancellationRequested";);
}
System.Diagnostics.Debug.WriteLine("Before Call Connect ...";);
ServiceAccess.ConnectToServerAsync(cancellationToken);
System.Diagnostics.Debug.WriteLine("After Call Connect ...";);

}, cancellationToken);

try
{
await someTask;
System.Diagnostics.Debug.WriteLine("try await someTask";);
}
catch(OperationCanceledException ex)
{
System.Diagnostics.Debug.WriteLine("OperationCanceledException ex";);
}
finally
{
System.Diagnostics.Debug.WriteLine("finally";);
}
}


ServiceAccess ConectToServerAsync(Cancellationtoken token)
{
public IServiceAsync ConnectToZyanAsync(CancellationToken token)
{
if (token.IsCancellationRequested)
{
System.Diagnostics.Debug.WriteLine("Cancellation observed in Service Access.";);
return null;
}
zyanConn = new ZyanConnection(zyanServiceUrl, protocol, _credentials, false, true);
.... .... ....
return _iServiceAsync;
}

Wenn ich nun OK Klicke, und innerhalb von 2000 ms "Abbrechen" klicke habe ich folgende Ausgabe:

SomeTask is Running...
Cancel Button Clicked
cancellationToken.IsCancellationRequested
Before Call Connect ...
Cancellation observed in Service Access.
Operation was canceled as expected.
After Call Connect ...
try await someTask
finally

Nehme ich den Thread.Sleep raus und klicke OK, und dann abbrechen erhalte ich folgende Ausgabe:

SomeTask is Running...
Before Call Connect ...
Cancel Button Clicked
finally

Wenn beim erreichen von "Before Call Connect" kein Cancel gedrückt wurde , läuft er durch und gibt kein zurück mehr, drücke ich vorher Cancel, wird der Verbindungsbefehl nicht abgesetzt.
Deswegen die Sleep Gedenkpause, merke ich "ooops, habe falsche url eingetippt, und drücke abbrechen" kann ich noch korrigieren, ansonsten muss ich auf Exception "Server nicht gefunden warten"

Kann man das so machen?

Danke

EDIT: habs nun so gelöst, denke das taugt für meinen Zweck :

5 Sekunden Gedenkzeit

  1. Wird innerhalb von 5 Sek Abbrechen Button gedrückt abgebrochen, wird der Verbindungsaufbau nicht aufgerufen, der Status ändert sich in " Vorgang wird abgebrochen" (muss ja noch die 5 Sek abwarten, nach 5 Sekunden dann StatusText: Vorgang abgebrochen

  2. Wird kein Abbrechen gedrückt, kommt irgendwann SocketException "Es konnte keine Verbindung hergestellt werden, da der Zielcomputer die Verbindung verweigerte"

  3. Wird Abbrechen gedrückt nachdem die 5 Sek. Denkpause abgelaufen sind und der Verbindungsbefehl bereits abgesetzt wurde, kommt trotz Abbrechen die Socketexception. Die lasse ich dann einfach unterm Tisch fallen... weil ohne Verbindung soll eh nichts passieren, und wenn der User Abbrechen klickt, wird er wohl auch nichts mehr erwarten 😁 👍

04.05.2015 - 15:25 Uhr

Hallo Abt,

Entschuldige. Wie du sagst, ca. 10 minuten nur nach Infos geschaut,...

Meine Aussage ist daher als Wunschdenken zu interpretieren und war mehr ironisch gemeint.

04.05.2015 - 14:47 Uhr

Ahjaaa. ok 🙂 zu viel neues ist nicht gesund ...
Verstehe das aber so dass demnächst meine WinForm Anwendung auf Linux und Mac cool? Cool ... 8) 8)

Danke für die Info.

04.05.2015 - 14:27 Uhr

Hi,

habe ein Projekt auf .NET 4.5 umgestellt. Danach steht bei den DLL's Laufzeiteversion Kompiliert mit v4.0.30319

Laufzeitversion soll doch die .NET Version darstellen mit der die Assembly kompiliert wurde?

Nun habe ich mal ein neues Projekt angelegt, Zielframework 4.5 , und auch da steht überall als Laufzeitversion v.4.0.30319....

Müsste da nicht v4.5.xxxx stehen? 🤔

04.05.2015 - 14:20 Uhr

Teamfähig sein 😁

04.05.2015 - 07:32 Uhr

Hi,

danke für eure Antworten.

Dann werde ich mal versuchen meine Anwendung Asynchron aufzubauen ...

Gut das ich kein Architekt bin, das Haus wäre schon 20 mal wieder abgerissen worden 😁

Auf Asynchron umbauen, impliziert dann auch die darunter liegende Schichten? Also Server/Service muss auch Asynchrone Methoden bereitstellen?

Werde mich erst noch einlesen, da ich mit asynchron noch gar nichts gemacht habe. Dann finde ich bestimmt noch einige Stolperfallen.

Sollte ich das .net 4.5 verwenden ?

03.05.2015 - 13:03 Uhr

Hallo,

danke für eure Antworten.

async/await.. quasi so?


private async void OKButton_Click(object sender, EventArgs e)
{
            cts = new CancellationTokenSource(); 

            try 
            { 
                await ProcessConnectSQLCancelleable(cts.Token);
            }
            catch (OperationCanceledException)
            {
            }
}

 private async Task ProcessConnectSQLCancelleable(CancellationToken cancellationToken)
        {
            try
            {
                string connString = "Data Source=*****;Initial Catalog=***;Integrated Security=SSPI";
                connection = new SqlConnection(connString);
                await connection.OpenAsync(cancellationToken);
            }
            catch (SqlException sqlEx)
            {
                System.Diagnostics.Debug.WriteLine(sqlEx.Message);
            }
        }


Wenn ich richtig gelesen habe wäre es doof ein Async await nur für "den verbindungsaufbau" zu benutzen. Sollte ich dann auf den rest der Anwendung auch übertragen ?

Um z.B. später mehrere SQL Querys gleichzeitig ausführen zu können?

30.04.2015 - 19:04 Uhr

Ich versuchs nochmal anders 😃

Das Problem: z.B. Falsche Eingabe von ServerURL , oder Server TimeOut, dauert ewig und friert die GUI ein.

Ziel: Den Verbindungsaufbau in ein eigenen Thread packen, so dass man noch die möglichkeit hat "Abbrechen" zu klicken, wenn einem die Falscheingabe auffällt, bevor Server nicht gefunden oder sonstwas erst nach einiger Zeit kommt.

Danke.

30.04.2015 - 17:16 Uhr

Hmm. Okay. Ich weiß grad nicht ob die Schnittstelle zum Service was zum Abbrechen bereit stellt...

Was wäre denn bei dem SQL DB Beispiel?


 void logonWorker_DoWork(object sender, DoWorkEventArgs e)
{
connection = new SqlConnection(connString);
connection.Open();
}


 private void cancelButton_Click(object sender, EventArgs e)
{
    if (_logonWorker.IsBusy)
   {
         _logonWorker.CancelAsync();
         connection.Close();
   }
}

Klicke ich hier auf Abbrechen passiert auch "nichts", im Hintergrund läuft es weiter bis nach einer Minute oder zwei die Exception kommt , TimeOut abgelaufen, SQL Server nicht erreichbar ...

Wie würde ich in diesem Fall den Verbindungsaufbbau abbrechen?

30.04.2015 - 16:01 Uhr

Hi,

meine WinForm Anwendung hat ein Login Formular, beim Click auf OK wird versucht die angegebene Server URL zu kontaktieren/verbinden. Während versucht wird zu verbinden friert mir die Anwendung ein.

Nun möchte ich den Verbindungsaufbau in einen anderen Thread legen. Das habe ich mit einem Backgroundworker gelöst.


public void okButton_Click(object sender, EventArgs e)
        {
            _logonWorker = new BackgroundWorker();
            _logonWorker.DoWork += new DoWorkEventHandler(logonWorker_DoWork);
            _logonWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(logonWorker_WorkCompleted);
            _logonWorker.WorkerSupportsCancellation = true;
            _logonWorker.RunWorkerAsync();
        }

 private void cancelButton_Click(object sender, EventArgs e)
        {
            _logonWorker = new BackgroundWorker();
            if (_logonWorker.IsBusy)
            {
               _logonWorker.CancelAsync();
            }
}

im DoWork


void logonWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            if (_logonWorker.CancellationPending)
            {
                e.Cancel = true;
                return;
            }
            try
            {
              ServiceAddress = LogInForm.Instance.ServerPath;
               Service.Users.LogOn(LogInForm.Instance.UserName, LogInForm.Instance.Password);               
            }
           catch(Exceptions ex){}
}

void logonWorker_WorkCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Error != null)
    {
       throw e.Error;
    }
}

Nun scheint sich das ganze so zu verhalten, das einmal die Aufforderung zur Verbindung abgesetzt wurde, diese trotz Canceln des Backgroundworker weiter läuft, und irgendwann dann den Error im Hintergrund zurückgibt das nicht verbunden werden konnte, bzw. Server/Url nicht erreichbar.

Das gleiche habe ich ncoh mit einer SQL Connection, da dauert es auch "ewig" bis ich die Response bekomme das der Server nicht erreichbar ist, dort versuche ich ein


try
{
connection.Open();
}

Was müsste ich machen damit der abgesetzte Verbindungsversuch auch abgebrochen wird, und nicht trotz abbrechen im Hintergrgund weiter versucht wird den Server zu finden ?

Mit Thread arbeiten statt Backgroundworker? Und dann versuchen den Thread bei Abbrechen zu killen?

Danke

30.04.2015 - 15:36 Uhr

Hi,

vielleicht hilft der Thread hier auch noch was beizutragen. 👍

22.04.2015 - 16:21 Uhr

Nach aktuell geltendem Recht haften beide Seiten, der Anwender und der Entwickler.

Demnach sollte ich mal bei uns vorschlagen Microsoft zu verklagen weil unser MS Dynamics NAV es immer wieder schafft Fehlerhafte Buchungsposten zu generieren, u.U. sogar in abgeschlossene Geschäftsjahre bucht 8o 8o

Ich darf gerade Artikelposten suchen (und versuchen zu korrigieren) bei denen Artikel einen Lagerbestand von 0 haben, aber einen Lagerwert aufweisen. Dabei ist die Logik doch ganz einfach, wenn nichts im Lager ist, kann es auch nichts Wert sein... Sind meistens Rundungsfehler, aber rein theoritisch müsste Dyamics Nav dafür extra Rundungsposten erstellen, was es (bei uns) nicht tut...

Anwendungsfehler?, Programmfehler?

Nach Schema F funktioniert alles, aber es gibt immer wieder Sonderfälle die auftauchen, wo auch keiner weiß wie die Zustande gekommen sind, und dementsprechend schwer zu korrigieren sind.

Die Steuerprüfer freuen sich immer wenn Sie ein Artikel mit 10 Zu und 10 Abgängen und 50 Korrekturbuchungen vorgelegt bekommen ...

20.04.2015 - 13:49 Uhr

Normalerweise testest Du Business-Logik (Services) und den DAL (Repository) einzeln mit Hilfe von Interfaces, Dependency Injection und Mocks als Unit-Tests. Diese haben keine fixe Reihenfolge, sondern es geht hier um den Test jeder einzelnen Methode.

Okay. Nächster Versuch 😁

Es geht um den Test einzelner Methoden... okay, und wenn ich dann in meiner BL eine Methode habe (wie im Ausgangsthread)


public IList<Customer> GetAllCustomers()
{
  if (RoleManager.IsInRole("Employee"))
  {
      return _customerRepository.GetAllCustomers().ToList();
  }
}

und im RoleManager


 public bool IsInRole(string role, string username))
{
  return UserManager.getUserByName(username);
}

Ist das eigentlich schon murks zum testen,

Habe ich im CustomerManager:


 public IList<Customer> GetAllCustomers()
        {
            if (_isInRole)
            {
                var customers = _customerRepository.GetAllCustomers().ToList();
                return customers;
            }
            else
            {
                throw new SecurityException("ApplicationServer.LocalizedAccessDeniedMessage Kein Employee rechte");
            }
        }

dann ist es einfacher zu testen:


[TestMethod]
        public void TestGetAllCustomers()
        {
            IQueryable<Customer> customersQueryableMockList = new List<Customer>{
                    new Customer { No = "1", Name = "Peter1",Address = "grüner weg"},
                    new Customer { No = "2", Name = "Peter2",Address = "blauer weg"},
                    new Customer { No = "3", Name = "Peter3",Address = "gelber weg"}
                    }.AsQueryable();


            var mockCustRepo = new Mock<ICustomerRepository>();
            mockCustRepo.Setup(mcr => mcr.GetAllCustomers()).Returns(customersQueryableMockList);
           
            var _target1 = new CustomerManager(mockCustRepo.Object);
            _target1.IsInRole = true;
            
            var result = _target1.GetAllCustomers();

            Assert.AreEqual(result.Count,3);
        }

Mit anderen Worten, ich brauch besseren Code, damit ich besser testen kann?

17.04.2015 - 16:17 Uhr

Hi,

schwere Nuss zu knacken... 8)

also ich möchte eigentlich nur die Business Logik testen... Ohne erst den Service starten zu müssen, und ohne den Datenbankserver Starten zu müssen ...

Die GUI zapft den Service an.
Der Service hat (soll) keine Logik , er hat Zugriff auf den CustomerManager(BL), und ohne Datenbankzugriff kann mir auch erstmal egal sein was unterhalb vom BL passiert.

Hab jetzt kapiert dass wenn ich mein CustomerRepository mit einer UniTest testen möchte, ich nicht drum herum komme die Datenbank zu mocken, also entweder den Context mocken, oder aber einen eigenen doppelten FakeContext erstellen...

Habe beide Varianten mal probiert, und dennoch wird versucht eine Connection auf den SQL Server zu bekommen, wahrscheinlich wegen dem Connection String in meinem Context...

Ich wollte anfangen den Customer Repository zu testen, weil habe mir gedacht ist die unterste Schicht, so kann ich dann evtl. eher Abhängikeiten von Darüber liegenden Schichten herausfinden ... aber naja, wohl doch keine so gute Idee mit dem Customer Repository zu testen...

Okay. Also teste ich mal den Service welcher der Server bereitstellt, das funktioniert immerhin:


 public void GetCustomerByNo_Test()
        {
            //Arrange
            Customer customer = new Customer { No = "2", Name = "TEST-CUSTOMER", Address = "TEST-ADDRESS" };
            var mock1 = new Mock<ICustomerManager>();
            mock1.Setup(m => m.GetCustomerByNo("3")).Returns(customer);
            var _target1 = new CustomerService(mock1.Object);
            //Act
            var result1 = _target1.GetCustomerByNo("3");
            //Assert
            Assert.AreEqual(customer.No, result1.No);

        }

[TestMethod]
        public void GetAllCustomers_Test()
        {
            //Arrange
            var mock = new Mock<ICustomerManager>();
            mock.Setup(m => m.GetAllCustomers()).Returns(customersMockList);
            var _target = new CustomerService(mock.Object);
            //Act
            var result = _target.GetAllCustomers();
            //Assert
            Assert.AreEqual(result.Count(), 3);
        }

Soweit so gut. Auf die gleiche Weise wollte ich nun den CustomerManager(BL) testen,
dieser benötigt bei mir aber ein UnitOfWork, dem ein Context übergeben werden muss, und ein CustomerRepository dem das UnitOfWork übergeben werden muss.

Dann muss ich, um den CustomerManager zu testen, das UnitOfWork und das Repository Mocken? Anders geht nicht?


    public class UnitOfWork : IUnitOfWork
    {
        public PKADBContext Context { get; set; } // unitof work holding a context
       
        public UnitOfWork(PKADBContext dbContext)
        {
            Context = dbContext;
        }

        public void Commit()
        {
            Context.SaveChanges();
        }
    }


public class CustomerManagerBL : ICustomerManager
{
        private ICustomerRepository _customerRepository;
        private IUnitOfWork _unitOfWork;         
        
         public CustomerManager(ICustomerRepository icustomerRepo, IUnitOfWork iuow)
        {
            _customerRepository = icustomerRepo;
            _unitOfWork = iuow;
        }

        public IList<Customer> GetAllCustomers()
        {
             if (RoleManager.IsInRole("Employee",currentUser))
            {             
             return _customerRepository.GetAllCustomers().ToList();
             }
        }

}

Das zu testen mit:


 //Arrange
var mock = new Mock<ICustomerRepository>();
mock.Setup(m => m.GetAllCustomers()).Returns(customersQueryableMockList);
 var _target = new CustomerManager(mock.Object);
//Act
var result = _target.GetAllCustomers();
//Assert
Assert.AreEqual(result.Count(), 3);

Funktioniert nicht weil in der Funktion GetAllCustomers die Abfrage IsInRole vorhanden ist.
Was dazu führt das der Test irgendwo im IUserRepository fehl schlägt weil Context Null ist

Also doch alles Mocken ? Quasi so?


[TestMethod]
        public void GetAllCustomersFromCoustomerManager_Test()
        {
            //Arrange
            var mockSet = new Mock<IDbSet<Customer>>();
            var mockContext = new Mock<IDBContext>();
            mockContext.Setup(mc => mc.Set<Customer>()).Returns(mockSet.Object); 
            var mockUOW = new Mock<IUnitOfWork>();
            mockUOW.Setup(muow=>muow.IContext).Returns(mockContext.Object);
            var mockRepo = new Mock<ICustomerRepository>();
            mockRepo.Setup(cr => cr.GetAllCustomers()).Returns(customersQueryableMockList);

            var _target1 = new CustomerManager(mockRepo.Object, mockUOW.Object);
            //Act
            var result = _target1.GetAllCustomers();
            //Assert
            Assert.AreEqual(result.Count(), 3);

        }

Vielleicht ist es auch nur das Vermurkste drum herum warum es bei mir nicht klappt , bzw. so richtig Click hat's auch noch immer nicht gemacht, erstmal wieder sacken lassen, hab schon Alpträume , hehe ...

16.04.2015 - 17:45 Uhr

Mach erst mal Unit Tests

Ja, ich versuch das mal, aber so ganz verstehe ich das mit dem Mocken nun nicht...

Ich wollte ein Unit Test schreiben um zu prüfen ob in mein CustomerRepository die Methode GetAllCustomers aufgerufen werden kann, dann habe ich mich wieder verhaspelt, habe gedacht CustomerRepository braucht ja ein UnitOfWork, UnitOfWork ein Context, dann war ich ruck zuck bei FakeContext erstellen, um das EF zu mocken, und schien mir als wäre das wieder in richtung Integrationstest gegangen ...

Also nochmal alles zurück ... und nochmal von vorne ...

Ich habe:


public interface ICustomerRepository : IRepository<Customer>
{
     Customer GetCustomerByNo(string custNo);
     IQueryable<Customer> GetAllCustomers();
}

 public class CustomerRepository : Repository<Customer>, ICustomerRepository
    {
        private IUnitOfWork _unitOfWork;

        public CustomerRepository(IUnitOfWork unitOfWork)
            : base(unitOfWork)
        {
            _unitOfWork = unitOfWork;
        }

        public Customer GetCustomerByNo(string custNo)
        {
            var o = _unitOfWork.Context.Customers.Where(s => s.No == custNo).FirstOrDefault<Customer>();
            return o;
        }

        public IQueryable<Customer> GetAllCustomers()
        {
            return this.GetAll();
        }
}


Der Unit Test:


 var mockCustRepo = new Mock<ICustomerRepository>();
mockCustRepo.Setup(mcr => mcr.GetAllCustomers()).Returns(It.IsAny<IQueryable<Customer>>);
mockCustRepo.Object.GetAllCustomers();
mockCustRepo.Verify(mcr => mcr.GetAllCustomers(), Times.AtLeastOnce(), "Fail Message");
Assert.AreEqual(mockCustRepo.Object.GetAllCustomers(), typeof(IQueryable<Customer>));

Was aber mache ich hier eigentlich?

Also, Mock auf ICustomerRepository
Mit dem Setup lege ich fest das ich ein IQueryable<Customer> erwarte?
mockCustRepo.Object.GetAllCustomers(), weil sonst Error: "Invocation was not perfomed on the mock
Und Verify prüft ob die Methode ausgeführt werden kann?
Assert meldet ein fehler wenn GetAllCustomers nicht vom Typ IQueryable ist...

Und wofür mache ich das ? 🤔

Wenn ich doch jetzt hingehe, und den Rückgabe Type von GetAllCustomers ändere in IEnumerable oder IList, dann meldet die Intelisense ja schon dass da was nicht mehr passt, hmm...

Also mal mit die andere Methode Testen, GetCustomerByNo :


 [TestMethod]
        public void Testwas()
        {
            var customer = new Customer { No = "2", Name = "TEST-CUSTOMER", Address = "orange weg " };
            
            // Arrange
            var mockcustomerRepository = new Mock<ICustomerRepository>();
            mockcustomerRepository.Setup(mcr => mcr.GetCustomerByNo("2")).Returns(customer);
            
            //Act
            var returnedCustomerFromRepository = mockcustomerRepository.Object.GetCustomerByNo("2");

            //Assert 
            Assert.AreEqual(customer.No, "2");
            Assert.AreEqual(customer.Address, returnedCustomerFromRepository.Address);
}

Hier prüfe ich?

Ob ich der Methode GetCustomerByNo einem String übergeben kann und ob ich dann einen Customer zurück bekomme? Und Aufgrund des Mock wird folgender Part in CustomerRepository.GetCustomerByNo nicht aufgerufen?


 var o = _unitOfWork.Context.Customers.Where(s => s.No == custNo).FirstOrDefault<Customer>();

Bin jetzt auch einige Moq Beispiele in Zusammenhang mit Unit Test von codeProject durchgegangen, aber so richtig schlaue werde ich da auch nicht, irgendwie fällt es mir schwer zu verstehen was da getestet wird.

Vielleicht könnt Ihr mir nochmal auf die Sprünge helfen.

Danke

15.04.2015 - 15:18 Uhr

Wenn du einen Integrationstest machen willst, dann willst du doch gar nicht mocken.
Abt ging es wohl hauptsächlich darum, dir zu erklären, wie man einen Unit-Test vernünftig aufsetzt (und dafür dann eben ein Mocking-Framework zusammen mit DI einsetzt, um eben genau nur eine Methode zu testen, ohne die unterliegenden Schichten).

Ja. Hab kapiert das dass was ich als Unit Test dachte, ein Integrationstest ist.

Aber wenn man es anders besser machen kann, oder später einfacher ist zu testen, lasse ich mich gerne belehren und versuche zu verstehen wofür so ein Unit Test ist. bringt mir ja nichts wenn ich jetzt "meinen Integrationstest" fortführe, und später merke ich dann oh Unit test wäre doch besser gewesen.

Wenn das erstellen von Unit Tests helfen kann die fehlende Dependency Injection einzusetzen ist mir damit auch geholfen. Abt hatte mir da im anderen Thread schonmal drauf hingewiesen das mir diese fehlt, nun werde ich wieder damit konfrontiert. Werde wohl erstmal DI verstehen müssen bevor ich mich Unit Tests versuche.

Hier auch noch mal ein Artikel für die Unterschiede:
>

While all devs tend to prefer unit tests, newbies tend to write integration tests, without actually knowing it. 😁 8o

15.04.2015 - 13:19 Uhr

Hi,

okay, also mal Festhalten dass das was ich machen wollte/will ein Integrationstest ist.
Ich mag quasi von der GUI bis zur DB "mal eben schnell" durchtesten ohne erst den ServiceServer zu starten mich in der GUI durchzuklicken usw ...

Hab mir nun mal MOQ besorgt um die Unit test zu verstehen...

CustomerService wird bereitgestellt vom Server
CustomerManager soll mein Business Layer sein
CustomerManager ruft CustomerRepository auf.

Es gilt bei Unit test also unabhängig zu testen. Soweit verstanden.

Normalerweise testest Du Business-Logik (Services) und den DAL (Repository) einzeln mit Hilfe von Interfaces, Dependency Injection und Mocks als Unit-Tests. Diese haben keine fixe Reihenfolge, sondern es geht hier um den Test jeder einzelnen Methode.
Die Namespaces wären quasi

Ok.

Wenn ich nun in der Customer Busines Logic (bei mir ist das CustomerManager) etwas testen möchte, sollte das so aussehen?


IList<Customer> customersMockList = new List<Customer>{
                    new Customer { No = "1", Name = "Peter1",Address = "grüner weg"},
                    new Customer { No = "2", Name = "Peter2",Address = "blauer weg"},
                    new Customer { No = "3", Name = "Peter3",Address = "gelber weg"}
                    }; 

[TestMethod]
       public void TestCustomerManager()
        {
            Mock<ICustomerManager> customerManagerMock = new Mock<ICustomerManager>();

            // return all customers
            customerManagerMock.Setup(c => c.GetAllCustomers()).Returns(customersMockList);
            
            // return a Customer by No
            customerManagerMock.Setup(crm => crm.GetCustomerByNo(
                It.IsAny<string>())).Returns((string custNo) => customersMockList.Where(
                x => x.No == custNo).Single());

            // Try finding a Customer By No 
            Customer singleCustomer = customerManagerMock.Object.GetCustomerByNo("2");

            // Get All Customers
            IList<Customer> customerList = customerManagerMock.Object.GetAllCustomers();

            // try CreateCustomer
            Customer newCustomer = new Customer() { No = "23", Address = "regenbogen weg" };
            customerManagerMock.Object.AddCustomer(newCustomer);
            customerManagerMock.Verify(c => c.AddCustomer(newCustomer), Times.AtLeastOnce());
            customerList.Add(newCustomer);
            

            foreach (Customer c in customerList)
            {
                System.Diagnostics.Debug.WriteLine("Customer no: " + c.No + " and lives in " + c.Address);
            }
            System.Diagnostics.Debug.WriteLine("Test CustomerManager Customer no: " + singleCustomer.No + "lives in " + singleCustomer.Address);
            Assert.IsNotNull(customerList);
        }

Was prüft dieser Test ?

Ob CustomerManager AddCustomer ein Objekt Customer empfangen kann und auch eins wieder zurückgibt ?
Und ob die Methode GetCustomerByNo einen String empfängt , mit dem er die Customer Liste durchsuchen soll und ein Customer zurück gibt?

So wie prüfen ob GetAllCustomers eine Liste von Customer zurückgeben kann?

Alles andere in der Methode wird quasi ignoriert? Also dem test oben interessiert es nicht ob in der CustomerManager.GetCustomerByNo eine Rollen Prüfung oder sonst was statt findet?


   public Customer GetCustomerByNo(string custNo) 
        {
            if (RoleManager.IsInRole("Employee", currentUser))
            {
                return customerRepository.GetCustomerByNo(custNo);
            }
            if (custNo = "2") 
            {
                throw new SecurityException("Alarm: Böser Customer");
            }
            return null;
        }

Und das Customer Repository würde man so testen ?


[TestMethod()]
        public void TestCustomerRepository()
        {
            //var contextMock = new Mock<PKADBContext>();
            //var unitOfWorkMock = new Mock<IUnitOfWork>();
            
Mock<ICustomerRepository> customerRepositoryMock = new Mock<ICustomerRepository>();
            
            // return all customers
            customerRepositoryMock.Setup(c => c.GetAllCustomers()).Returns(customersQueryableMockList);

            // return a Customer by No
            customerRepositoryMock.Setup(crm => crm.GetCustomerByNo(
                It.IsAny<string>())).Returns((string custNo) => customersQueryableMockList.Where(
                x => x.No == custNo).Single());

            // Try finding a Customer By No 
            Customer testCustomer = customerRepositoryMock.Object.GetCustomerByNo("2");
            System.Diagnostics.Debug.WriteLine("CustomerFound by No : " + testCustomer.No + " and lives in " + testCustomer.Address);
            
            // Get All Customers
            IQueryable<Customer> customerList = customerRepositoryMock.Object.GetAllCustomers();
            
        
            Assert.IsNotNull(customerList);

        }

Also nur mal so Grundlegend, mir ist klar das die Tests wohl Nonsens sind,
nur um zu verstehen ob die so isoliert genug sind.

Und ich brauch quasi für alles ein Interface, also am besten noch ein ICustomer den ich auch zum Mocken benutzen kann, ICustomerManager, ICustomeRepository, ICustomerService habe ich...

Eine Sache aber noch

Diesen Mock anschließend dem Repository übergibst und prüfst, ob Add() korrekt aufgerufen wurde.

Hatte gedacht das geht so :


var unitOfWorkMock = new Mock<IUnitOfWork>();
            Mock<ICustomerRepository> customerRepositoryMock = new Mock<ICustomerRepository>(unitOfWorkMock);

Aber geht nicht weil Constructor Arguments cannot passed to interface mocks.

Danke für eure Geduld 👍

14.04.2015 - 15:17 Uhr

Hmm. Dann bring ich da wohl doch was durcheinander....

Brauche ich dann ein Integrationstest? Weil ich habe ja sonst noch nichts was ich testen könnte?

Sieht momentan bei mir so aus:


using System;
using elTorito.BusinessLogic;
using DataTier.Models;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace BusinessLogicTests
{
    [TestClass()]
    public class CustomerTests
    {
        Customer _customer;

[TestMethod()]
        public void CreateCustomertest()
        {
            Customer dummyCustomer = new Customer() { No = "Fistro", Address = "Grüner Weg 16", CountryCode = "DE", CreatedBy = "Peter ", CreatedOn = DateTime.Now, ChangedBy = "Peter ", ChangedOn = DateTime.Now };
            Customer _savedCustomer = CustomerManager.CreateCustomer(_dummyCustomer);
            Assert.IsNotNull(_savedCustomer);
        }

        [TestMethod]
        public void TestCustomerAddition()
        {
            int a = 1;
            int b = 2;
            int c = 3;
            int expectedResult = 6;
            int result = CustomerManager.CustomerAddition(a, b, c);
            Assert.AreEqual(expectedResult, result);
        }
}


Und CustomerManager


using System;
using System.Data;
using System.Collections.Generic;
using System.Linq;
using DataTier.Models;
using DataTier;
namespace Zyan.elTorito.BusinessLogic
{
public static class CustomerManager
    {
private static readonly CustomerRepository customerRepository = new CustomerRepository(IUOW);        

[PrincipalPermission(SecurityAction.Demand, Role = BusinessPrincipal.EmployeeRoleName)]
        public static void CreateCustomer(Customer customer)
        {
            customer.CreatedBy = "Peter";
            customer.CreatedOn = DateTime.Now;
            customer.ChangedBy = "Peter";
            customer.ChangedOn = DateTime.Now;

            customerRepository.Add(customer);
            Commit();
        }

        public static int CustomerAddition(int a, int b, int c)
        {
            if (a ==0) 
               throw new Exception("a ist 0, das muss gemeldet werden !!! ");            
            
            return a + b + c;
        }
}

}

Wenn es eine Unit Test werden soll ist die Testmethode oben falsch weil Sie
using elTorito.BusinessLogic;
using DataTier.Models;

referenziert? Und das soll nicht bei einem Unit test?

Wenn dann aber jegliche Referenz fehlt auf das was ich eigentlich testen möchte. Wie teste ich das dann? 🤔 Will ja nicht den Code Doppel schreiben.

So wie ich das verstehe, möchte elTorito aber hier gar keinen reinen Unit-Test machen (d.h. Mocken der untergeordneten Schichten), sondern explizit die komplette BL aufrufen, um seine Übergabeparameter auf Vollständigkeit zu überprüfen.

Ja, ich glaub langsam auch ich hab das mit dem Unit Test falsch verstanden. Hatte Gedacht kann eine Wiedergabeliste mit TestMethoden erstellen, die alle nach und nach abarbeiten ... Aber okay.Das ist dann wohl Integrationstest.

Ich guck mir nun aber mal das mit dem MOQ an , vielleicht versteh ich dann doch noch wofür der Unit test ist 🙂

14.04.2015 - 14:39 Uhr

Hi,

danke für eure Antworten.

ein UnitTest testet ein Stück Code. Was du hier machst hat schon Integrationstest-Charakter.

Um den CustomerService zu testen würde man diesem eine Interface von CustomerManager injezieren.
Man testet dann nur, ob das Interface korrekt aufgerufen wurde.
In weiteren Unit-Tests würde man die korrekte Implementierung von CustomerManager testen.

Also den Service wollte ich nicht testen. CustomerService leitet von ICustomerService ab,
und der Service macht eigentlich nichts weiter als die Methoden im BL (CustomerManager) aufzurufen. Bis auf "CustomerManager.CurrentUser = ServerSession.CurrentSession.Identity.Name;" was ich jetzt zusätzlich reingefummelt habe.

ICustomerService nutze ich als Schnittstelle zum GUI

Also, ich möchte testen ob der BL (CustomerManager) die Daten die er bekommt weiterverarbeiten kann, bzw. korrekt zurückgibt.

Wenn ich über meine GUI teste steigt mir der Debugger meistens im BL aus , so z.b. habe ich für Customer ein Pflicht Feld "geändert am" , das Feld soll im BL gesetzt werden bevor der Datensatz in die DB gespeichert wird. Das hatte ich noch nicht implementiert, und als mir das Programm zum xten mal wegen dem vergessenen Pflichtfeld ausgestiegen ist wollte ich mir ein Unit test schreiben damit ich schnell testen kann ob ich nicht noch was vergessen habe.

Und auch der User sollte übergeben und nicht ermittelt werden.
Normalerweise testest Du Schicht für Schicht.

Ja, okay, wenn ich den überge, muss ich den im RoleManager nicht ermitteln, so wie ich es gerade mache (ServerSession.CurrentSession.Identity.Name) ist doof weil hier RoleManager abhängig ist vom Service.

Lies Dir mal [Artikel] Unit-Tests: Einführung in das Unit-Testing mit VisualStudio durch

Ja, habe ich. Ich denke das habe ich auch verstanden 😃


        [TestMethod]
        public void TestCustomerAddition()
        {
            int a = 1;
            int b = 2;
            int c = 3;
            int expectedResult = 6;
            int result = CustomerManager.CustomerAddition(a, b, c);
            Assert.AreEqual(expectedResult, result);
        }

Daas gleiche wollte ich ja für:


[TestMethod()]
        public void CreateCustomertest()
        {
            Customer dummyCustomer = new Customer() { No = "Fistro", Address = "Grüner Weg 16", CountryCode = "DE", CreatedBy = "Peter ", CreatedOn = DateTime.Now, ChangedBy = "Peter ", ChangedOn = DateTime.Now };
            Customer _savedCustomer = CustomerManager.CreateCustomer(_dummyCustomer);
            Assert.IsNotNull(_savedCustomer);
        }

Wenn ich hier kein _savedCustomer zurückbekomme weiß ich da ist was falsch (oder in der darunter liegenden Schicht EF/Datenbank), also ist der Test CreateCustomertest auch nicht richtig weil er abhängig ist von dem EF/Repository dessen Daten er zurückgibt?

14.04.2015 - 10:45 Uhr

VS 2012, MS UnitTestFramework

Hi,

ich versuche eine Unit Test zu erstellen, ich möchte einen Business Layer testen, ob dieser einen Customer einfügen, bearbeiten, löschen kann.


namespace BusinessLogicTests
{
    [TestClass()]
    public class CustomerTests
    {
        Customer _customer;
        [TestMethod]
        public void CustomerTestMethods()
        {
            GetAllCustomers();
//            GetCustomerByNo();
//            DeleteCustomer();
//            CreateCustomer();
//            UpdateCustomer();
        }

        [TestMethod]
        public void GetAllCustomers()
        {
            Assert.IsNotNull(CustomerManager.GetAllCustomers());
        }
    }

Im CustomerManager habe ich eine Rollenprüfung:


 /// <summary>
        /// Holt Alle Customer aus der Datenbank 
        /// </summary>
        /// <returns></returns>
        public static IList<Customer> GetAllCustomers()
        {
            if (RoleManager.IsInRole("Employee"))
            {
                var customers = customerRepository.GetAllCustomers().ToList();
                return customers;

            }
            else
            {
                throw new SecurityException("ApplicationServer.LocalizedAccessDeniedMessage Keine Employee rechte");
            }
        }

Und im RoleManager:


 public static bool IsInRole(string role)
{
 foreach (Rolle r in UserManager.GetUserByAccountName(ServerSession.CurrentSession.Identity.Name).Rolles)
            {
                if (String.Compare(r.Name, role,
                    StringComparison.InvariantCultureIgnoreCase) == 0)

                    return true;
            }

            return false;
}

Der Test fällt durch weil in RoleManager ServerSession.CurrentSession.Identity.Name nicht bekannt ist. Da ich darauf kein Einfluss habe weil vom Service Server kommt und Schreibgeschützt ist, muss ich dass ja irgendwie simulieren.

Also habe ich gedacht simuliere ich einen Principal:


 [TestMethod()]
        public void LogOn()
        {
            Thread.CurrentPrincipal = Helper.SetSamplePrincipal();
        }

Damit wäre es möglich im RoleManager auf Thread.CurrentPrincipal.Identity.Name zu prüfen:


 public static bool IsInRole(string role)
{
 foreach (Rolle r in UserManager.GetUserByAccountName(Thread.CurrentPrincipal.Identity.Name).Rolles)
            {
                if (String.Compare(r.Name, role,
                    StringComparison.InvariantCultureIgnoreCase) == 0)

                    return true;
            }

            return false;
}

Demnach müsste ich an der Stelle abfragen ob ich mich im test befinde und mein Beispiel Principal abfrage, oder ob ich mich in der Echt Version befinde und ServerSession.CurrentSession.Identity.Name abfrage...

Die Abfrage ob man sich im test befindet oder nicht, soll wohl gehen, aber sehr schlecht sein.

Wie kann ich das Simulieren?

Einzige was mir einfällt ist dass der Service im CustomerManager den UserName setzt:


public class CustomerService : ICustomerService
    {

        public CustomerService()
        {
            CustomerManager.CurrentUser = ServerSession.CurrentSession.Identity.Name;
        }
        public IList<Customer> GetAllCustomers()
        {
            return CustomerManager.GetAllCustomers();
        }
}

Und dann IsInRole abändern das Rolle und UserName übergeben werden :


public static bool IsInRole(string role, string currentUserName)
{
 foreach (Rolle r in UserManager.GetUserByAccountName(currentUserName).Rolles)
            {
                if (String.Compare(r.Name, role,
                    StringComparison.InvariantCultureIgnoreCase) == 0)

                    return true;
            }

            return false;
}

Kann man das so machen? Oder ganz schlecht und besser ganz anders?

Danke

Gruß
Peter

08.04.2015 - 12:50 Uhr

Danke. 👍

Aufrufliste ist auswählbar während der Debugger Aktiv ist (Debugger -> Fenster -> Aufrufliste)

Danke auch für den Tipp mit den Breakpoints Bedingungen.

08.04.2015 - 11:08 Uhr

Hi,

ich versuche herauszufinden warum an einer bestimmten Stelle kein Wert ankommt.


public GetObjektByString meinObjekt(string meinString)
{
 string s = meinString;
}

meinString ist null... sollte er aber nicht...

die Aufrufhierarchie zeigt mir das die Methode GetObjektByString von so einigen aufgerufen wird,
nur wie finde ich raus welche von denen nun den leeren String übergeben hat ?

Einzige was mir nun einfällt ist bei jedem Aufrufer ein Breakpoint zu setzen und dort gucken ob der String leer ist.

Gibt es eine Art "Rückwärtssuche"? Wo ich an der Stelle wo der Debugger aussteigt nachverfolgen kann welche Methoden zuvor aufgerufen werden?

Ich kenn dass so vom Dynamics NAV Debugger. Dann geht das bestimmt auch im VS ?

Danke
gruß
Peter

02.04.2015 - 13:31 Uhr

@elTorito: sicher?
Ohne virtual wird quasi eager-loading erzwungen, sowohl bei Navigation Properties wie auch bei Scalar Properties.
Würde mich ja doch sehr wundern, wenn das seit dem EF6 anders sein soll (auch wenn es einem POCO eher gerecht wird).

Ja.

Wobei nicht ausgeschlossen ist dass ich an einer anderen Stelle was krum habe 😃

Wenn ich aber hier und hier lese, scheint das so zu sein.

02.04.2015 - 12:03 Uhr

das liegt mit Sicherheit am automatisch generierten Code oder an der bereits bestehenden Datenbank...

👍

Hab die Schlüssel in der Users_Roles Table geändert, dort brauche ich ja keine eigene ID als Primary Key, jeder User kann mindestens eine Role haben. Also PK auf UserID und RoleID gesetzt.

Jetzt habe ich wieder **user.Roles **

virtual weglassen und es wird direkt geladen (>kannst auf das include verzichten).
virtual = lazy loading.

Wenn ich Virtual und Include() weg lasse, werden die Roles nicht geladen ...

werden nur geladen mit


var user = _unitOfWork.Context.Users.Include("Roles").Where(s => s.UserAccount == username).FirstOrDefault<User>();

oder


var user = _unitOfWork.Context.Users.Where(s => s.UserAccount == username).FirstOrDefault<User>();
            _unitOfWork.Context.Entry(user).Collection(s => s.Roles).Load();

Vielen Dank.
gruß
Peter

02.04.2015 - 10:47 Uhr

Hi..
Du musst hier also dein "include" erweitern.

_unitOfWork.Context.Users.Include("Users_Roles.Role").Where(s => s.UserAccount == username).FirstOrDefault<User>();  
  

Dann ist auch User.User_Roles[0].Role nicht mehr null.

lg

Stimmt. Danke.

Aber so richtig "übersichtlich"/"Ordentlich" finde ich das nicht...

Dass kommt wahrscheinlich von der automatischen Code Generierung ? Die mich verwirrt?

Weil der User soll ja eine Auflistung von Role haben und nicht Users_Roles , oder?

02.04.2015 - 09:43 Uhr

verwendetes Datenbanksystem: SQL 2008, EF 6

Hi,

ich bin dabei meine Anwendung auf EF umzustellen. (Datenbank vorhanden)

Ich habe folgende Tabellen:
Users
Roles
Users_Roles

Tabelle Users und Roles besteht aus 2 Spalten : Guid und Name, wo Guid der PK ist
Tabelle Users_Roles besteht aus 3 Guid Spalten (ID, UserID, RoleID), wo ID = PK ist, und UserID und RoleID jeweils ein Fremdschlüssel auf die Tabelle Roles.RoleID und Users.UserID haben

Ein User kann mehrere Role haben.

Mit den EF PowerTools mache ich Reverse Engineer Code First.
Ich erhalte folgende Modelle:


public partial class User
    {
        public User()
        {
            this.Users_Roles = new List<Users_Roles>();
        }

        public System.Guid ID { get; set; }
        public string UserAccount { get; set; }
        public virtual ICollection<Users_Roles> Users_Roles { get; set; }
    }

public partial class Role
    {
        public Role()
        {
            this.Users_Roles = new List<Users_Roles>();
        }

        public System.Guid ID { get; set; }
        public string Name { get; set; }
        public virtual ICollection<Users_Roles> Users_Roles { get; set; }
    }

public partial class Users_Roles
    {
        public System.Guid ID { get; set; }
        public System.Guid UserID { get; set; }
        public System.Guid RoleID { get; set; }
        public virtual Role Role { get; set; }
        public virtual User User { get; set; }
    }

LazyLoadingEnabled und ProxyCreationEnabled sind false.

Mit folgender Funktion hole ich mir den User:


public User GetUserByUserAccountName(string username)
{
    var user = _unitOfWork.Context.Users.Include("Users_Roles").Where(s => s.UserAccount == username).FirstOrDefault<User>();
return user;
}

Als Ergebnis erhalte ich 2 User_Roles:

User.User_Roles[0]
User.User_Roles[1]

Aber verstehe nicht so ganz die Daten welche die User_Roles beinhalten:

User.User_Roles[0].ID = (PK in der Users_Roles Tabelle)
User.User_Roles[0].RoleID = (Guid der Role)
User.User_Roles[0].UserID = (Guid des User)
User.User_Roles[0].Role = Null
User.User_Roles[0].User = User.User_Roles[0]

Früher hatte mein User die Eigenschaft Role[], und ich hatte Manuell
Role[] userRoles = "Select alle Rollen die zur UserID passen"

Dann hatte ich eine Abfrage:


 foreach (Role role in userRoles){
                if (String.Compare(role.Name, EmployeerRoleName,
                    StringComparison.InvariantCultureIgnoreCase) == 0)
                {
                    // Current user is a member of employee role.
                    return;
                }
            }

Das funktioniert natürlich nicht mehr :


            foreach (Users_Roles r in userRoles)
            {
                if (String.Compare(r.Role.Name, EmployeerRoleName,
                    StringComparison.InvariantCultureIgnoreCase) == 0)
                {
                    // Current user is a member of employee role.
                    return;
                }
            }

weil User.User_Roles[0].Role = Null ist.

  1. sind meine Tabellenbeziehung korrekt?

  2. Wie bekomme ich eine Auflistung von Roles im User Objekt?

Statt


public virtual ICollection<[B]Users_Roles[/B]> Users_Roles { get; set; }

bräuchte ich doch eigentlich ein?:


public virtual ICollection<[B]Role[/B]> Users_Roles { get; set; }

Ist das jetzt falsch weil der Code Automatisiert generiert wurde? oder habe ich ein Denkfehler?

Danke

26.03.2015 - 13:53 Uhr

Das Fazit ist nun? Hats geklappt?

Ja. 😁 ich denke schon 👍

Ich hab den Aufruf nun in mein "BusinessLayer"(CountryManager) eingebaut:


public static class CountryManager
{
        private static IUnitOfWork IUOW = new UnitOfWork(new PKADBContext());
        private static CountryRepository countryRepository = new CountryRepository(IUOW);
        private static UserRepository personRepository = new UserRepository(IUOW);

        public static Country CreateCountry(Country country)
        {
            countryRepository.Add(country);
            return country;
        }

        public static IList<Country> GetAllCountries()
        {
            IList<Country> countryFullList = countryRepository.GetAllCountriesInTable().ToList();
            return countryFullList;
        }

        public static User CreatePerson(User user)
        {
          personRepository.Add(user);
        }

        public static void Commit()
        {
            IUOW.Commit();
        }

}

In meinem Country Service:


public class CountryService : ICountryService){
   public void AddCountry(country,person)
   {
     CountryManager.CreateCountry(country);
     CountryManager.CreatePerson(person);
     
     CountryManager.Commit();
   }
}

Ich krieg den Dreh schon noch raus ... 🙂

26.03.2015 - 09:14 Uhr

Und zwei Contexte zeitgleich macht kein Sinn.
Wenn dann nur einer von beidem...

Ja, sorry. Habe ich falsch dargestellt. Das meinte ich mit dem

//oder

Entweder ein EFContext, oder ein PKAContext oder ein MongoDatabase Context mit rüber geben.

Hätte besser neue

 Abschnitte mit eingefügt :)
25.03.2015 - 19:09 Uhr

Okay.

ich brauch also quasi nur das Interface zum Context


    public interface IDBContext 
    {
        PKAExampleContext PKAContext { get; }// Context für PKA
        DbContext EFContext {get;} // Context für EF
        //MongoDatabase MongoContext {get;} // Context für MongoDB
}

 public class MyDBContext : IDBContext
    {
 public PKAExampleContext PKAContext { get; private set; }
        public DbContext EFContext { get; private set; }
}

Dann muss ich dem Unit Of Work nur irgendein Context übergeben ?


IUnitOfWork IUOW = new UnitOfWork(new MyDBContext(new PKAExampleContext()));
//oder
IUnitOfWork IUOW = new UnitOfWork(new MyDBContext(new EFContext()));

18.03.2015 - 15:47 Uhr

Hallo Abt,

hab mir mal dein MongoRepository angeschaut, und habe mal versucht die Trennung von

IDBContext
IUnitOfWork
IRepository

vorzunehmen...

Aber ich hab da noch immer Knoten, 🤔, ja, ich weiß ist schwer zu verstehen, vor allem wenn auch noch Grundlagen fehlen, ich versuch trotzdem irgendwie ein Muster zu finden auf dem ich aufbauen kann, damit ich später, wenn es komplexer wird, einfacher Lösungen implementieren kann, ich denke mal wenn einmal die Basis steht, und funktioniert, kann man auch eher verstehen was und warum und wie... Ist natürlich nur ein Gedanke ...

Also....

Im Business Layer:


public class CountryBL
{
   public void TesteWas(){
            IUnitOfWork IUOW = new UnitOfWork(new MeinDBContext());
            UserRepository userRepository = new UserRepository(IUOW);
            CountryRepository countryRepository = new CountryRepository(IUOW);

            IList<User> userFull = userRepository.GetAllUsersInTable().ToList();           
            IList<Country> countryFullList = countryRepository.GetAllCountriesInTable().ToList();

            var user = new User {ID=Guid.NewGuid(),  FirstNames= "Test Person", LastName= "last name " };
            personRepository.Add(user);

            IUOW.Commit();
   }
}

Unit Of WORK


    public interface IUnitOfWork
    {
     IDBContext Context { get; set; }  // unitof work holding a context
        void Commit();
    }

    public class UnitOfWork : IUnitOfWork
    {
        public IDBContext Context { get; set; } // unitof work holding a context

        public UnitOfWork(IDBContext dbContext)
        {
            Context = dbContext;
        }

        public void Commit()
        {
            Context.SaveChanges();
        }
    }

IDBContext


public interface IDBContext 
{
        DbSet<TEntity> Set<TEntity>() where TEntity : class;
        int  SaveChanges();
        void Dispose();
        DbEntityEntry Entry(object entity);
}

DBContext


    public class DBContext : PKADBContext,IDBContext
    {
        public DBContext(PKADBContext dbContext)
        {
            Context = dbContext;
        }
        public PKADBContext Context { get; private set; }
    }

User Repository


        public class UserRepository : Repository<User>, IUserRepository
        {
             public UserRepository(IUnitOfWork unitOfWork): base(unitOfWork)
            {}
            public IQueryable<User> GetAllUsersInTable()
           {
              return this.GetAll();
           }
        }

        public interface IUserRepository : IRepository<User>
        {
           IQueryable<User> GetAllUsersInTable(); 
        }

Basis Repository


public class Repository<TEntity> : IRepository<TEntity> where TEntity : class

    {
        private readonly IUnitOfWork _unitOfWork;

        public Repository()
        {
        }

        protected Repository(IUnitOfWork unitOfWork) // Each repository holds a unitof work
        {
            _unitOfWork = unitOfWork;
        }
        public virtual IQueryable<TEntity> GetAll()
        {
            return _unitOfWork.Context.Set<TEntity>();
        }

        public virtual void Delete(TEntity entity)
        {
            _unitOfWork.Context.Set<TEntity>().Remove(entity);
        }

        public virtual void Delete(int id)
        {
            var entity = GetById(id);
            if (entity == null) return; // not found; assume already deleted.
            Delete(entity);
        }

        public virtual TEntity GetById(int id)
        {
            return _unitOfWork.Context.Set<TEntity>().Find(id);
        }

        public virtual void Update(TEntity entity)
        {
                _unitOfWork.Context.Set<TEntity>().Attach(entity);
        }

        public virtual void Add(TEntity entity)
        {
            _unitOfWork.Context.Set<TEntity>().Add(entity);    
        }
}


public interface IRepository<TEntity> where TEntity : class
{
        IQueryable<TEntity> GetAll();
        TEntity GetById(int id);
        void Add(TEntity entity);
        void Update(TEntity entity);
        void Delete(TEntity entity);
        void Delete(int id);
}

Wäre das eine Struktur auf die man aufbauen kann?

Der IDbContext ist nun aber dafür da, dass Du die DB-Engine sehr schnell tauschen kannst, zB von EF auf MongoDB.

Wenn ich nun also statt der SQL DB eine MongoDB nehmen möchte,
dann müsste ich quasi DBCONtext von MongoDatabase ableiten?

public class MyDBContext : MongoDatabase,IDBContext
{
}

der UoW Container wird in 5 Repositories injeziert; zwei andere Repository bekommen aber einen ANDEREN UoW-Container (in Summe = 2).


IUnitOfWork IUOW1 = new UnitOfWork(new MeinDBContext());
IUnitOfWork IUOW2 = new UnitOfWork(new MeinAndererDBContext());

UserRepository userRepository = new UserRepository(IUOW);
CountryRepository countryRepository = new CountryRepository(IUOW1);


Und dabei könnte z.B. MeinDBContext die DB auf einem SQL Server sein, und MeinAndererDBContext könnte auf ein anderen SQL Server liegen?

Falls ich das Unit Of Work weg lassen möchte (theoritisch), müsste ich dann den Repository den DataContext statt UnitOfWork übergeben? Und würde so vom BL übers Repository auf die DB gehen?

        
public UserRepository(DBContext context):base(context)
        {
        }

Hoffe bin auf den richtigen Weg. Erstmal wieder genug für heute.

16.03.2015 - 17:58 Uhr

Hallo Abt,

danke für deine Geduld...

Ich versuch nochmal ein paar Schritte zurück ...

Der Context ist meine Datenbank ?
UnitOfWork repräsentiert mein Context, also die Datenbank.

Wenn ich UnitOfWork dann wie folgt hätte:


    public class UnitOfWork : IUnitOfWork
    {
        public DbContext Context { get; set; }

        public UnitOfWork()
        {
            Context = new DatenModellVonDB(); // Mein Context, Entities
        }
....

Müsste ich dem Repository dann den UnitOfWork mitgeben?


    public class RepoCountry: BasisRepository<Contact>
    {
        
        public RepoCountry(IUnitOfWork unit):base(unit)
        {

        }

    }


        public BasisRepository(IUnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork;
            this.dbSet = _unitOfWork.Db.Set<T>();
        }

Dann hätte ich im BusinessLayer Zugriff wie folgt:


            uow = new UnitOfWork();
            Country[] c = new RepoCountry(uow).GetCountryNamesWithCode("DE").ToArray();

Wahrscheinlich jetzt auch doof an der Stelle ein Neues Repository zu erstellen... aber ich versuche mich zu sortieren, wo was hingehört, ich vermute da noch immer Knoten im Gedanken.

Aber so ist dem Unit of Work nicht bekannt welche Repositories es gibt. 😁 Oder?

Ok. Hab Kopfschmerzen. Nochmal drüber schlafen ...

16.03.2015 - 15:00 Uhr

Hallo Abt,

danke für deine Antwort.

Ich würde das anders machen.

IDBContext ist die Datenbankverbindung.
IUnitOfWork repräsentiert Dein Context
IRepository das spezifische Repository.

Mit Containern meinst du jeweils eine DLL ?

Wieso also manche Beispiele die Repositories in die Klasse des Containers tun ist für mich ein riesen Fragezeichen..

Sowas wie CreateDBContext im Konstruktor... wie willst Du sowas sauber Testen?

An testen habe ich noch nicht gedacht, das habe ich so aus einem der zahleichen Beispiele übernommen...


protected void CreateDbContext()
{
DbContext = new PKADBContext();
DbContext.Configuration.ProxyCreationEnabled = false;
DbContext.Configuration.LazyLoadingEnabled = false;
DbContext.Configuration.ValidateOnSaveEnabled = false;

PS: POCOs haben keine Attribute wie Key.
Das deckst Du im EF und Code First mittlerweile über das Modelling ab.

Über das Mapping?


 public class CountryMap : EntityTypeConfiguration<Country>
    {
        public CountryMap()
        {
            // Primary Key
            this.HasKey(t => t.ID);
           .... .... ...
         }
 }

Ist nicht einfach zu verstehen das ganze. Gibt sooo viele Beispiele, und jeder erklärt es anders, und zwischendurch stolpert man immer wieder darüber dass es eigentlich gar kein Sinn ergibt das Muster so anzuwenden... Harte Nuss :evil:

16.03.2015 - 11:46 Uhr

Hi,

ich nochmal 😁 ... so ganz verstanden habe ich es immer noch nicht X(

Habe deswegen nun meine Anwendung mal beiseite gelegt und versucht ein neues "klares" Beispiel zu erstellen in der Hoffnung dass wenn die Komplexität der Anwendung "ausgeblendet" ist es mir etwas klarer wird.

ich habe angefangen mit einer Library "Models" dort befinden sich einfache POCO's :


    public class Country
    {
        [Key]
        public Guid Id { get; set; }
        public string CountryName { get; set; }
        public string CountryCode { get; set; }
    }

Die Models werden in alles andere Verwiesen. Der Aufbau ist derzeit wie folgt:

  1. ConsoleApp
  2. Servicess Access Layer
  3. Zyan Server
  4. Business Layer
  5. Data Layer

Bisher hatte ich meine SQL DB über den SQL Manager gestaltet, dass versuche ich nun über die POCO's in der Model Library und Code First.

In der Console App:


 ServiceAccess SA = new ServiceAccess();
Country[] cl = SA.GetCountries();

Den Service Access benötige ich um die Datenquelle auf Client Seite auszuwählen, in diesem Fall den Zyan Server, könnte aber genauso gut ein WebService oder ein anderes Medium sein welches ein Services zur Verfügung stellt.

Der Zyan Server stellt den CountryService zur Verfügung:


    public class CountryService : ICountryService
    {

        public Country[] GetAllCountries()
        {
            return CountryManager.GetAllCountries();
        }
    }

CountryManager liegt im Business Layer (DLL)


 public class CountryManager
    {
        public static Country[] GetAllCountries()
        {
               IUnitOfWork uow = new UnitOfWork();
              
               //return uow.Countries.GetCountryNamesWithCode("DEU").ToArray();
               return uow.Countries.GetAll().ToArry();
        }
   }

Im DataLayer befinden sich die Repositories, Unit Of Work, Context, Configuration, Datenbank Connection....

Dort habe ich ein Repository Countries:


    public class RepoCountry : Repository<Country>, IRepoCountry
    {

        public RepoCountry(DbContext context)
            : base(context)
        {

        }

        //new customize method for countries
        //gets countries with specific code
        public IQueryable<Country> GetCountryNamesWithCode(string countryCode)
        {
            return GetAll().Where(p => p.CountryCode == countryCode.ToLower());
        }
    }

    public interface IRepoCountry : IRepository<Country>
    {

        //new customize method for countries
        //gets countries with specific code
        IQueryable<Country> GetCountryNamesWithCode(string countryCode);
    }

Interface UnitOfWork:


    public interface IUnitOfWork
    {
        // Save pending changes to the data store.
        void Commit();

        // Repositories
        IRepository<Skill> Skills { get; }
        IRepoCountry Countries { get; }
    }

UnitOfWork:


public class UnitOfWork : IUnitOfWork, IDisposable
    {
        private PKADBContext DbContext { get; set; }
        
public UnitOfWork()
        {
            CreateDbContext();


        }

        //repositories
        #region Repositries
        private IRepository<Skill> _skills;
        private IRepoCountry _countries;

        //get Country repo
        public IRepoCountry Countries
        {
            get
            {
                if (_countries == null)
                {
                    _countries = new RepoCountry(DbContext);

                }
                return _countries;

            }
        }

}

Ich denke sollte Simple genug sein um weiter vorzugehen....

So... Jetzt habe ich ja meinen Business Layer von wo aus ich das Unit of Work anspreche um mir meine Daten zu holen.

Im BusinessLayer wird derzeit ein UnitOf Work initalisiert, im Falle von hinzufügen von Daten werden diese Committet, das heißt, ich kann dort eine Prüfung durchführen, und wenn die Daten irgendwie "korrupt" sind, kann ich ein Rollback ausführen in dem ich nicht Commite?

Ich kann also ein UnitOFWork initialisieren, und dort dann diverse Sachen behandeln, z.B. Land einfügen, Person einfügen, etc... und es wird dann alles zusammen in ein Rutsch an die Datenbank übertragen?

Der DBContext, der in der UnitOfWork dem repository "übergeben" wird, repräsentiert quasi den Zusammenhalt von UnitOfWork und den Repositories?

Dann habe ich 2 Repositories:


public IRepository<Certification> Certifications
{
}

public IRepoCountry Countries
{

}

Der Unterschied ist dass IRepository<Certification> , ein "einfaches" Repository ist, und IRepoCountry ein benutzerdefiniertes Repository ist? Das benutzerdefinierte Repository brauche ich z.B. wenn ich Benutzerdefinierte Abfragen erstelle, wie z.B. Get Countrys by Code ? Während ich für einfaches CreateReadUpdateDelete immer von IRepository ableiten kann ?

Bin ich auf den richtigen weg ?

Danke

28.01.2015 - 10:11 Uhr

Muß es denn unbedingt ein altes VS (2008, 2010) sein? Ansonsten nimm einfach
>

Hi, ne muss nicht unbedingt sein, wäre nur "Nice to have", weil das VS2008 brauche ich auf jedenfall für die NAV RDLC Reports , wäre natürlich schön gewesen wenn ich mit der gleichen Version , mit der ich die RDLC Reports bearbeite, auch C# debuggen könnte.

Nachdem ich gemerkt habe das dass Debuggen angehängter Prozesse mit der VS 2010 Expres auch nicht geht, bin ich grad dabei das VS 2013 Desktop version am installieren.

Wenn ich richtig verstanden habe ist Community nichts anderes als die 3 Express Versionen zusammengefasst (Web, Apps, Desktop) , ich brauch ja nur Desktop

EDIT: So, hab das VS 2013 Express Desktop installiert. Damit kann ich den Prozess debuggen!
Hat die Firma wieder Geld gespart 😉

Vielen Dank euch für die Hilfe.

27.01.2015 - 14:09 Uhr

Ja, genau die Version scheint das zu sein "Business Inteligence bla blup" für Reports, Analysis usw...

Mit dieser Version kann ich vermutlich auch nicht "ein *cs File zum Debuggen an ein Prozess hängen? 🤔

Puh... wie mache ich das nun? 🤔

Ich weiß zwar nicht wann die Firma das Update auf NAV 2013 plant, aber da ist es dann so das man dort RDLC Reports nur mit VS 2010 bearbeiten kann.

Welche wäre denn die kleinste VS2010 Version mit der man auch C# "Debuggen kann in dem man Prozesse anhängt" (wie würde man das korrekt ausdrücken? ) Ich denke du weißt was ich meine?

Der NAV RTC Client generiert (im Debugmodus) beim starten etliche *.cs Files, wenn ich debuggen möchte, müsste ich mit dem VS die entsprechende *cs dateien öffnen, meine Breakpoints setzen, und das ganze dann an den Server Prozess hängen, nach Adam Riese sollte dann der VS Debugger an der passenden stelle anhalten.

Dann muss ich mir jetzt mal überlegen warum ich ein VS 2010 brauche, um etwas zu Debuggen (RTC Client) der in der Firma nicht genutzt wird...

27.01.2015 - 13:35 Uhr

Hallo,

habe keine DVD,
könnte es sein das dass VS2008 was ich habe mit dem SQL Server 2008 installiert / bereitgestellt wurde ?

27.01.2015 - 12:23 Uhr

Hi,

ich hab ein VS 2008 SP1 😄, brauche ich für die RDLC Reports von Dynamics NAV, wollte nun versuchen den Dynamics NAV 2009 RTC Client mit dem VS2008 zu debuggen. Dabei ist mir aufgefallen das ich kein Syntax Highlighting für das VS 2008 habe, auch kein C# ... Also wenn ich Datei Neu mache, habe ich keine C# Vorlage, unter Extra - Optionen beim Texteditor auch nichts...

Weiß jemand wie ich C# da nachträglich rein bekomme?

Danke

17.11.2014 - 13:21 Uhr

Hallo Abt,

die Idee meines "Konstrukt" war zu versuchen "Module" zu isolieren.

Meine Struktur (Ohne EF) ist derzeit folgende:

  1. Client (in mein Fall WinForm, könnte aber auch Web, Phone, sonstwas sein)
  2. Shared Business Layer, (Pro Forma, falls ich auf Clientseite doch noch irgendwas behandeln muss)
  3. Services Access Layer

Der Service Access Layer soll den Zugriff auf den Service Anbieter Managen, also in mein Fall der Zyan Server, es könnte aber auch eine Lokale Beispiels Datenbank sein, oder ein WebService

  1. Zyan Server
  2. Business Layer (holt sich die Daten vom DAL, hier habe ich auch noch andere Logik wie z.B. ob der User Berechtigt ist sich Daten zu holen)
  3. Data Access Layer (Holt sich die Daten aus der DatenBank, in mein Fall SQL, wandelt in Objekte um und gibt an den BAL weiter)

Ich habe bisher eine DataObjects.dll wo meine Objekte abgebildet sind, die DataObjects ist derzeit allen anderen bekannt.

Der Server und der ServiceAccessLayer sowie Services kennen eine Interfaces.dll

Und so sollte man das nicht machen?

Die Idee ist: die darüber liegende Schicht / Anwendung interessiert nicht, wo die Daten liegen.

Die Idee verfolge ich ja, oder versuche es 😃
Mein BAL kennt den DAL, und der DAL liefert Ihm ein Objekt zurück. Mein Derzeitiger DAL macht eine SQL / Storage Procedure Abfrage, und wandelt den DataTable in eins meiner Objekte um.

Wenn ich nun das EF implementieren möchte, müsste ich doch eigentlich nur hier, also ab dem BAL eingreifen ?

Also statt :


namespace Zyan.elTorito.BusinessLogic
{
    public class CountryManager
    {
        public static Country[] GetAllCountries()
        {
          return CountryDAL.GetAllCountries();
        }
    }

müsste folgendes möglich sein ?


namespace Zyan.elTorito.BusinessLogic
{
    public class CountryManager
    {
        public static Country[] GetAllCountries()
        {
          return CountryEntityFrameWork.GetAllCountries();
        }
    }

Und wenn man das Repository Pattern berücksichtigen möchte, müsste es so sein?


namespace Zyan.elTorito.BusinessLogic
{
    public class CountryManager
    {
        public static Country[] GetAllCountries()
        {
          return Repository.GetAllCountries();
        }
    }

Und Unterhalb von Repositories würde dann das EF kommen?

17.11.2014 - 11:18 Uhr

Hi,

danke für eure Antworten, die mir zeigen das ich so einiges nicht verstanden habe 🙂

Und: insgesamt ist der ganze Quellcode alles andere als gut zu bezeichnen und zeigt, dass Dir da die Grundlagen fehlen 😉

Bezogen auf das EF oder allgemein mein "Konstrukt" mit den diversen Dll's?

Hinter den Repository Pattern, wie es vorgestellt wurde, steckt einfach ein Grundlegende Idee, diese kann man verstehen oder nicht.

Was könnte helfen diese Idee zu verstehen? 🤔

14.11.2014 - 14:05 Uhr

Hallo witte,

danke für die Antwort.

Mit Repository Pattern ist so etwas hier gemeint?

Ich hab nun mehrfach gelesen das das Entity Framework schon so eine Art "Repository" sein soll, und das man deswegen nicht unbedingt dieses Muster braucht. Aber verstanden warum habe ich nicht.

Ich habe nun mal folgendes ausprobiert:

Erstmal EF 6 besorgt.

Und damit folgende Struktur gebastelt:

Eine neue Library: Mn.DomainModels.dll (wie in dem Beispiel oben beschrieben)

Library Person.Contracts.dll beinhaltet die (aus der Datenbank generierte EDMX Datei, in mein Fall Person.edmx), wie auch oben in dem Beispiel beschrieben, habe ich die Person.tt ausgelagert in Mn.Domainmodels.dll und die Person.Context.tt angepasst mit Verweis auf Mn.Domainmodel.dll

Auch in der Person.Contracts.dll befindet sich ein Interface IPersonService:


namespace Mm.DomainModel
{
    public interface IPersonService
    {
        /// <summary>
        /// Gibt eine bestimmtes Person anhand eines Schlüssels zurück.
        /// </summary>
        XUser GetXUser(Guid productID);

        bool AddUser(XUser user);
    }
}

Weiter habe ich eine Person.Business.dll welche auf Mn.DomainModels.dll und auf DataAccessLayer.dll verweist.

Eine Person.Service.dll welche auf Mn.DomainModels.dll und Person.Business.dll verweist.

Eine Person.WindowsGUI.dll welche auf Mn.DomainModels.dll und SharedBusiness.dll verweist.

In der DataAccessLayer.dll habe ich dann z.B. folgenden Code:


namespace elTorito.DataAccessLayer
{
    public class PersonDAL
    {
        public static bool AddUser(XUser varUser)
        {
            int changesSaved = 0;

            // Create the database context
            using (var dbContext = new PersonEntities())
            {
                // Construct a User object with the correct properties
                var user = new XUser
                {
                    UserName = varUser.UserName,
                    FirstName = varUser.FirstName,
                    LastName = varUser.LastName,
                    EmailAddress = varUser.EmailAddress
                };

                // Add the User object to the database context's Users collection
                dbContext.XUsers.Add(user);

                try
                {
                    // Save the changes to the database, and record the number of changes
                    changesSaved = dbContext.SaveChanges();
                }
                catch (System.Data.Entity.Validation.DbEntityValidationException dbEx)
                {
                    Exception raise = dbEx;
                    foreach (var validationErrors in dbEx.EntityValidationErrors)
                    {
                        foreach (var validationError in validationErrors.ValidationErrors)
                        {
                            string message = string.Format("{0}:{1}",
                                validationErrors.Entry.Entity.ToString(),
                                validationError.ErrorMessage);
                            // raise a new exception nesting
                            // the current instance as InnerException
                            raise = new InvalidOperationException(message, raise);
                        }
                    }
                    throw raise;
                }
            }


            // Return a bool based on whether any changes have been stored
            return changesSaved >= 1;
        }

        public static List<XUser> GetAllUsers(){
            // Create the database context
            using (var dbContext = new PersonEntities())
            {
                return (from c in dbContext.XUsers
                            select c).ToList();
            }
        }

    }

DataAccessLayer.dll verweist auf Person.Contracts.dll (wo das EDMX File liegt) und auf Mn.DomainModel.dll

Okay. Weiter...
Der Zyan Remoting Server verweist auf Mn.DomainModels.dll und Person.Service.dll

Dann habe ich meinen WinForm Client welcher auf Person.Windows.GUI.dll verweist, SharedBusiness und ServiceAccess welche beide Mn.Domain.Model.dll kennen.

Auf Client Seite ist also nur Person.Windows.GUI.dll und Mn.DomainModel.dll bekannt.

Der Zyan Server kennt Mn.DomainModel.dll und Person.Service.dll (wo das Interface in Mn.DomainModel bereitsteht)

Person.Service.dll kennt den Person.Business.dll und Mn.DomainModel.dll

Person.Business.dll kennt Mn.DomainModel.dll und den DataAccessLayer.dll

Soweit so gut. Ändere ich nun mit dem SQL Management Studio die Tabelle und füge eine Spalte hinzu, gehe ich in meine Person.Contract und rufe "Model aus Datenbank aktualisieren" auf. Anschliessend in Mn.Domainmodel.dll Rechter Mausklick auf person.tt und sage "Benutzerdefiniertes Tool ausführen" (was auch immer das bedeuten mag) und die XUser Klasse wird aktualisiert.

So habe ich das Problem das beim Speichern der Fehler kommt dass die Klasse User nicht Serialisierbar ist. Okay, also in Mn.DomainModel.dll noch folgendes reingeschrieben:


namespace Mm.DomainModel
{
    public interface IPersonService
    {
        /// <summary>
        /// Gibt eine bestimmtes Person anhand eines Schlüssels zurück.
        /// </summary>
        XUser GetXUser(Guid productID);

        bool AddUser(XUser user);
    }

    [Serializable]
    public partial class XUser
    {
    }
}

Führe ich das Beispiel so aus, fügt die Methode AddUser (im DAL mir einen neuen benutzer in die Tabelle ein).

Habe ich die Schichten so Sauber genug getrennt?
Welche Nachteile kann dieses Gerüst nun haben?

So wie es nun ist könnte ich jeder zeit die POCO'S Objekte übernehmen und den DAL austauschen?
Weil Person.Business.dll ja nur den DAl kennt:


 namespace Person.BusinessLogic
{
    public class PersonManager
    {
        public static bool AddUser(XUser user)
        {
            return XUserDAL.AddUser(user);
        }
        public static List<XUser> GetAllUsers()
        {
            return XUserDAL.GetAllUsers();
        }
    }
}

Hmm. Okay. Habe gerade mein Beispiel um ein GetAllUsers erweitert:
im DAtaAccessLayer:


public static List<XUser> GetAllUsers(){
            using (var dbContext = new PersonEntities())
            {
                return (from c in dbContext.XUsers
                            select c).ToList();
            }

Knallt es mit > Fehlermeldung:

Die Assembly "EntityFrameworkDynamicProxies-Mm.DomainModel kann nciht gefunden werden was scheinbar einer fehlenden Deserialisierung die Folge ist...

Also nochmal zurück auf Anfang und versuchen das mit dem Repository zu verstehen, oder De/Serialisierung behandeln?

Ai ai ai .... X( X(

13.11.2014 - 12:36 Uhr

verwendetes Datenbanksystem: SQL 2008R2
VS2012

Hi,

ich hab mich einige Zeit mit ADO .NET beschäftigt, und mir eine Anwendung gebastelt welche folgende Struktur hat:

* Ein, ich nenne es mal "Modul", bestehend aus 4 DLL's:
Artikel.BusinessLogic
Artikel.Contracts
Artikel.Services
Artikel.GUI

* Ein WinForm Client
* Shared Business Layer
* Service Access Layer

* Zyan Server
* Data Access Layer

Der Ablauf ist wie folgt:

  • Der WinForm Client kennt Artikel.GUI, und Artikel.Contracts und Shared Business Layer.
    Im WF Client wird die Artikel.GUI geladen, das zu ladende Objekt "Artikel" ist in Artikel.Contracts definiert. Um den Artikel zu laden ruft der WF Client eine Methode im SharedBusiness Layer auf (der auch Artikel.Contracts kennt).

Im Shared Business Layer wird ein Aufruf an Service Access Layer abgesetzt. der Service Layer verbindet mit dem Zyan Remoting Server.

Der Zyan Server verweist auf Artikel.Services und Artikel.Contracts, und ruft die passende Methode in Artikel.Service auf.

Artikel.Service wiederrum verweist auf Artikel.Business, und in Artikel.Business wird dann die passende Methode im Data Access Layer aufgerufen (welcher auch Artikel.Contracts kennt).


return ArtikelDAL.GetProduct(productID);

Es wird also ein Objekt vom Typ Artikel durchgeschleift bis zum WinForm Client.

Die ADO Geschichte funktioniert so wie ich es wollte. Also Grundgerüst aufgebaut, also kann ich anfangen damit meine Anwendung etwas komplexer zu gestalten, habe dann überlegt ob es ein weg gibt der die Sache etwas beschleunigen kann, und habe dann immer wieder gelesen das wenn die Anwendung komplexer wird, man einen O/RMapper nehmen sollte . Da ich gerne MS Hausmittel benutze also angefangen mich ins Entity Framework einzulesen und zu testen.

Gar nicht so einfach da den passenden Einstieg zu finden. Also erstmal Schnelleinstieg, und dann diverse Beispiele von Codeplex, Codeproject ausprobiert, aber so richtig richtig den Einstieg finde ich nicht.

Nun sehe ich bei den EF Beispielen immer wieder das die Entitäten durchgeschleift werden bis zur GUI. Bzw. die UI ein Verweis legt auf da wo sich das Entität Daten Modell befindet.

Nun weiß ich nicht ob ich meine Anwendung "remodellieren" soll, oder ob ich die Entitäten in "meine" Objekte umwandeln soll.

Hat es einen Nachteil wenn ich meinen Data Access Layer , der mir derzeit "eigene Objekte" (DataTables werden in Objekte konvertiert") ersetze durch das Entity Framework, und mir dann die Entitys in Objekte umwandeln lasse?

Ich würde nun folgendes versuchen:
Im Artikel.Contracts ist derzeit mein Objekt Artikel definiert und Artikel.Contracts stellt auch ein Interface bereit.

In Artikel.Contracts das Entität Model ablegen, und im Artikel.Business dann die Umwandlung von der Entität ins Objekt.

Ich bin mit den ganzen Begriffen noch nicht so vertraut, wie gesagt, finde nur schwer den Einstieg.

Wenn ich in der Artikel.Contracts ein Datenmodell (anhand meiner Tabellen) erstelle, wird ja auch automatisch das Objekt Artikel erstellt, welches dann Bestandteil von ein DatenContext ist. So wie auch die Listen / Collections der Verknüpfungen.

Die meisten UI's scheinen den DatenContext zu binden, wäre das der richtige Weg? Als dem WIn Client den DatenContext zur Verfügung stellen statt einer "dummen" Objekt Liste?

Vielleicht hat noch jemand ein Tipp für den Einstieg ins EF , über das MSDN will mir das nicht gelingen, es scheint auch stark von den Anforderungen abzuhängen wie man da vorgeht.

Was ich gerne möchte ist die Datenbank, wie gehabt, mit dem SQL Management Studio modellieren, und meine Anwendung soll die Datenmodelle anhand bestehender Tabellen erzeugen.

Und jetzt? Entität in Objekte umwandeln und alles andere lassen wie es war? Oder DatenContext bekannt geben?

Beim MSDN (z.B. hier) scheitere ich schon daran das ich dort nicht sehe welches EF gerade besprochen wird, und scheinbar macht es auch ein riesen Unterschied ob man EF 4, 5 oder 6 einsetzt.

Wenn ich richtig verstanden dann habe ich meine ersten Gehversuche mit EF 4 gemacht (nach dem erstellen der EDMX wurde zumindest EntityFramework v4.030319 als Verweis hinzugefügt)

Vielleicht hat jemand noch ein paar Gute Links auf Lager und evtl. paar Antworten auf mein Chaos

Danke.

05.11.2014 - 12:14 Uhr

Hallo Herbivore,

Danke für die Antwort. Das Focus() hatte ich noch hinzugefügt weil mir einfach nichts einfiel.

Habe nun im TreeView Click Event statt

openOrActivateCardChild(node.Form)

nun

 this.BeginInvoke(new Action(() => openOrActivateCardChild(node.Form)));

ChildForm wird geöffnet und bekommt den Fokus. Perfekt. 👍

Vielen Dank

05.11.2014 - 10:55 Uhr

Guten Morgen,

ich hab einen MDI Container, dieser beherbergt einen TreeView. Beim Clicken auf ein Node öffnet sich ein Child Form. Das Child Form erhält aber kein Focus 🙁

Nun scheint es beim treeView so zu sein das die Events in folgender Reihenfolge gefeuert werden:

  1. AfterSelect
  2. Enter
  3. GotFocus
  4. MouseUp

Das öffnen der Form hatte ich im Click Event des TreeView, nun habe ich es nach MouseUp verschoben:

private void TreeView_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
{
    case MouseButtons.Left:
    menuBarTreeView.SelectedNode = menuBarTreeView.GetNodeAt(e.X, e.Y);
    OpenOrActivateCardChild(menuBarTreeView.SelectedNode.Name);
    break;
}

in OpenOrActivateCardChild öffne ich das Child Form wie folgt in der Reihenfolge:


childForm.MdiParent = this;
childForm.Show();
childForm.BringToFront();
childForm.Activate();
childForm.Focus();

Im ChildForm, wird im Load Event :

meineTextBox.Focus ()

gesetzt.

Aber der zuvor geklickte Node im Tree bleibt Aktiv, und wenn ich Tasten drücke bewege ich mich zum nächsten Node, bzw. PageUp/Down im Tree hin und her...

Nun weiß ich grad nicht was ich noch probieren könnte um den Focus vom TreeView an das ChildForm zu übergeben. 🤔

Danke