Laden...

Benutzung von LiteDB mit Abstraktionsschicht

Erstellt von JimStark vor 3 Jahren Letzter Beitrag vor 3 Jahren 1.549 Views
JimStark Themenstarter:in
309 Beiträge seit 2020
vor 3 Jahren
Benutzung von LiteDB mit Abstraktionsschicht

Hi,

vielleicht kennt ja der ein oder andere LiteDB.
Da ein SQL-Server für dieses Projekt zu aufwendig wäre, würde ich gerne eine lokale Datenbank einsetzen. Vielleicht kennt ja jemand auch eine bessere Alternative.

Ich nehme als Beispiel wieder eine kleine Auftragsverwaltung - Auftrag - Kunde - Artikel.


    public class Article
    {
        public int ArticleID { get; set; }
        public string Title { get; set; }
        public Order Order { get; set; }
...

    public class Customer
    {
        public int CustomerID { get; set; }
        public string Firstname { get; set; }
        public string Lastname { get; set; }
        public List<Order> Orders { get; set; } = new List<Order>();
...
    public class Order
    {
        public int OrderID { get; set; }
        public string Title { get; set; }
        public Customer Customer { get; set; }
        public List<Article> Articles { get; set; } = new List<Article>();
...

Für die Datenabfrage habe ich mir einen kleine Abstraktionsschicht geschrieben:



    public class LocalDb : IAvService
    {
        private LiteDB.LiteDatabase _db;


        private ILiteCollection<Order> GetOrderCollection
        {
            get { return _db.GetCollection<Order>("orders"); }
        }
        private ILiteCollection<Customer> GetCustomerCollection
        {
            get { return _db.GetCollection<Customer>("customers"); }
        }

        private ILiteCollection<Article> GetArticleCollection
        {
            get { return _db.GetCollection<Article>("articles"); }
        }


        public LocalDb(string dbFile)
        {
            _db = new LiteDatabase(dbFile);
            BsonMapper.Global.Entity<Order>()
                .DbRef(x => x.Customer, "customers");
            BsonMapper.Global.Entity<Order>()
                .DbRef(x => x.Articles, "articles");
            BsonMapper.Global.Entity<Customer>()
                .DbRef(x => x.Orders, "orders");
            BsonMapper.Global.Entity<Article>()
                .DbRef(x => x.Order, "orders");
        }

        public Task<Customer> AddCustomer(Customer customer)
        {
            return Task<Customer>.Run(() =>
            {
                var col = GetCustomerCollection;
                var id  = col.Insert(customer);
                return col.FindById(id);
            });
        }
...

Erstelle ich einen neuen Auftrag mit Artikeln und Kunde löse ich das bisher so.
Da die Objekte (Artikel, Customer....) noch keine ID haben, muss ich sie erst separat in die Datenbank geben.


            IAvService av = new LocalDb("test.db");

            Order order = new Order
            {
                Title = "Test"
            };


            Article article1 = new Article
            {
                Title = "Arbeitszeit",
                Price = 60,
                Tax = 0.16
            };

            var dbArticle = av.AddArticle(article1).GetAwaiter().GetResult();

            Console.WriteLine($"Artikel ID: {dbArticle.ArticleID}");

            order.Articles.Add(dbArticle);

            order.Customer = av.AddCustomer(new Customer()
            {
                Firstname = "Max",
                Lastname = "Mustermann"
            }).GetAwaiter().GetResult();

            av.AddOrder(order);


Hier wollte ich fragen ob das soweit alles passt, da sich das ja durch mein komplettes Programm ziehen wird.
Was mich noch etwas stört ist bzw. was etwas unschön aussieht, dass ich alles erst Adden muss,
also es nicht direkt hinzufügen kann, wie bei EF: (Oder geht das doch irgendwie damit?)


Order order = new Order{
   Customer = new Customer{...}
}

W
955 Beiträge seit 2010
vor 3 Jahren

Was soll eigentlich

.GetAwaiter().GetResult()

bezwecken? Sind diese Addxxx-Methoden wirklich asynchron? Warum verwendest du dann nicht await (mit Task.ConfigureAwait(false))? Das sieht deadlock-verdächtig aus. Implementiere das gleich richtig wenn du es überall einsetzen willst.

16.806 Beiträge seit 2008
vor 3 Jahren

LiteDB hat kein Async support; das Wrappen in ein Task.Run() ist weit verbreiteter Workaround dafür.
Wenn man es dann aber wieder synchron umsetzt (.GetAwaiter().GetResult()) macht das wenig sinn; dann lieber alles nur Synchron anbieten.


    public class LocalDb : IAvService
    {
        private LiteDB.LiteDatabase _db;

Von der Umsetzung ist das soweit (grundlegend) korrekt; vom Naming im Sinne der Software Architektur nicht.
Im Endeffekt ist das weder ein Service, noch eine Db - sondern im Prinzip nichts anderes als (eine sehr einfache Variante) des Repository Pattern.

Falls Du Dich inspirieren lassen willst; ich hab zu LiteDB vor ein paar Jahren schon eine Lib geschrieben für ein damaliges Tool.
SchwabenCode.Data.LiteDBRepository

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 3 Jahren

Sind diese Addxxx-Methoden wirklich asynchron? Warum verwendest du dann nicht await (mit Task.ConfigureAwait(false))?

Nein, ich wollte es im Interface aber als Task definieren, dass ich später evtl. mal eine asynchrone REST-, RPC-Schnittstelle oder ähnliches dazwischen klemmen könnte.
Der Aufruf soll später auch mit await stattfinden, das war nur ein Testaufruf in einer Konsole.

Im Endeffekt ist das weder ein Service, noch eine Db - sondern im Prinzip nichts anderes als (eine sehr einfache Variante) des Repository Pattern.

Oh man, dieses Pattern vorher zu kennen wäre nützlich gewesen... 😁
Wen es interessiert, hier habe ich auch ein gutes Beispiel gefunden: Implementieren von Repository-und Arbeitseinheiten Mustern in einer ASP.NET MVC-Anwendung (9 von 10)

Vielleicht baue ich direkt auf deinen Code auf 😁

Danke euch beiden!

16.806 Beiträge seit 2008
vor 3 Jahren

Der Aufruf soll später auch mit await stattfinden, das war nur ein Testaufruf in einer Konsole.

Dann mach

public static async Task Main()

in Deiner Konsole.

Geht seit C# 7.1

Technisch gesehen macht der Compiler dann folgendes


// Wird automatisch erzeugt
public static void Main(string[] args)
{
    Main(args).GetAwaiter().GetResult();
}

// vom Entwickler
private static async Task Main(string[] args)
{
    ... // Main body here
}

Du merkst; wenn die Leute nicht Dein Code vollständig vor Augen haben und Du nichts erklärst, dann kommts zu Verwirrungen 😉