3

I've installed SQLite-Net Extensions package into my Xamarin.Android project in Visual Studio 2015 and I'm trying to implement many-to-many relationship between Person and Event entities. I've followed the official documentation of the library, but I can't make it working.

Classes I created:

Person.cs:

[Table("People")]
public class Person
{
    [PrimaryKey, AutoIncrement]
    public int Id { get; set; }

    public string Name { get; set; }

    public string LastName { get; set; }

    public string PhoneNumber { get; set; }

    public string Email { get; set; }

    [ManyToMany(typeof(PersonEvent), CascadeOperations = CascadeOperation.All)]
    public List<Event> Events { get; set; }
}

Event.cs:

[Table("Events")]
public class Event
{
    [PrimaryKey, AutoIncrement]
    public int Id { get; set; }

    public string Name { get; set; }
    public DateTime Date { get; set; }
    public string Place { get; set; }

    [ManyToMany(typeof(PersonEvent), CascadeOperations = CascadeOperation.All)]
    public List<Person> Participants { get; set; }
}

PersonEvent.cs:

public class PersonEvent
{
    [ForeignKey(typeof(Person))]
    public int PersonId { get; set; }

    [ForeignKey(typeof(Event))]
    public int EventId { get; set; }
}

To test it, I'm removing the database file to be sure a clean one is created on the device and then trying to create a Person, an Event, insert both into the DB first and then assign the Event to the Person and save again using UpdateWithChildren method:

var dbFile = new File(Constants.DbFilePath);
dbFile.Delete();

var db = new SQLiteConnection(new SQLitePlatformAndroid(), Constants.DbFilePath);
db.CreateTable<Person>();
db.CreateTable<Event>();
db.CreateTable<PersonEvent>();

var event1 = new Event
{
    Name = "Volleyball",
    Date = new DateTime(2017, 06, 18),
    Place = "Sports hall"
};

var person1 = new Person
{
    Name = "A",
    LastName = "B",
    PhoneNumber = "123456789"
};


db.Insert(person1);
db.Insert(event1);
person1.Events = new List<Event> { event1 };
db.UpdateWithChildren(person1);

First of all, I needed to add CreateTable<PersonEvent>() line because if it wasn't present I was getting the exception: SQLite.Net.SQLiteException: no such table: PersonEvent, however I thought I don't have to create this intermediate table manually. Shouldn't SQLite-Net Extensions create it automatically somehow?

With this line added, the first two inserts of event1 and person1 are working correctly (after inserting both entities have their Ids assigned). The Events collection on person1 has even the Event created, which also has Id set: enter image description here

However, the "opposite entity", in this case event1 has its Participants collection (List of Person) NULL:

enter image description here

I tried everything and nothing is working. As I understood reading SQLite-Net Extensions docs, it should be resolved automatically. I even added this CascadeOperations = CascadeOperation.All on the collections, but it doesn't help as you can see. I'm I still doing something wrong?

Dawid Sibiński
  • 1,657
  • 3
  • 21
  • 40
  • What happens when you get a person or event from the db? – Shawn Kendrot May 16 '17 at 21:07
  • @ShawnKendrot I've just tried and when I get the entities after the code I posted using for example for Person: db.Get(1), both collections (`Events` in `Person` and `Participants` in `Event`) are `NULL`. – Dawid Sibiński May 16 '17 at 21:10
  • @ShawnKendrot I tried and it changes nothing. I tried doing just one operation using `db.InsertWithChildren(person1)` (without those two individual `Insert`s before), but it doesn't change anything. – Dawid Sibiński May 16 '17 at 21:14
  • When you call `db.GetChildren(person1)` is the collection null, empty, or does it contain the child? – Shawn Kendrot May 16 '17 at 21:20
  • When I called `db.GetChildren` on both entities after retrieving them from the database using `db.Get(1)` and `db.Get(1)`, the collections on both entities contain the children. What could it mean then? – Dawid Sibiński May 16 '17 at 21:24
  • Ah, I think you need to use `db.GetWithChildren` instead of just using `db.Get`. – Shawn Kendrot May 16 '17 at 21:29
  • That's working, but I think it's not a solution. It only makes my `Person` entity have its `Events` collection not null and containing the children, BUT when I want to go into one if its `Events`, this `Event` has its `Participants` collection still `NULL`. That's because this `db.GetWithChildren` only "fills the 1st level" of children. According to the library's page (https://bitbucket.org/twincoders/sqlite-net-extensions/) in "Sample Code" section they just save the entities and the children are already initialized. I thought the purpose of this library is to resolve relations in runtime. – Dawid Sibiński May 16 '17 at 21:33

2 Answers2

2

The Events collection on person1 has even the Event created, which also has Id set

Your person1 object has a list of Events that you created. The collection contained a link to the event1 object that you created. This collection of events was not updated when you inserted or updated the database. You'll find that the Event object has it's Id set when you insert it into the database because the list contains the same object.

SQLite-NET Extensions is not a [auto] lazy-load solution (like Entity Framework). It will get objects only when it is told to. Using the Get method to get an object from the database does not do anything different than SQLite already does. This is because SQLite-NET Extensions does not alter that method. It does however offer new methods like GetWithChildren and FindWithChildren. If you want to get the children of an object you need the methods provided by SQLite-NET Extensions.

The samples provided use these methods as well:

// Get the object and the relationships
var storedValuation = db.GetWithChildren<Valuation>(valuation.Id);
if (euro.Symbol.Equals(storedValuation.Stock.Symbol)) {
    Debug.WriteLine("Object and relationships loaded correctly!");
}

You also wouldn't want the objects to recursively load from Person-Event-Person-Event-etc. as this would never end. If you are starting with a person and want to get all people associated with an event then you would want to get those in another call.

var person = db.GetWithChildren(1);
foreach(var event in person.Events)
{
    // lazy load ourselves
    var allParticipants = db.GetWithChildren(event.Id).Participants;
}

Hope that helps

Shawn Kendrot
  • 12,425
  • 1
  • 25
  • 41
  • Thank you, that clarifies. However, I wonder how most developers realize such relationships in their mobile app's databases ? Do they use some other kind of DB than SQLite? I found it most easy-to-use, but maybe there are better approaches of storing app's data persistently ? – Dawid Sibiński May 19 '17 at 17:57
  • I would guess that SQLite is used the most as far as databases are concerned. It's available on every platform, and is very easy to use – Shawn Kendrot May 19 '17 at 18:01
  • I've just found that Entity Framework (Core) can now be used with SQLite even for Xamarin.Android apps - https://blog.xamarin.com/building-android-apps-with-entity-framework/ . I need to give it a try. – Dawid Sibiński May 19 '17 at 18:04
  • Entity Framework Core [currently] does not have a good story for M-2-M relationships. You must declare the relationship table as a middle-man. See: https://github.com/aspnet/EntityFramework/issues/1368 – Shawn Kendrot May 19 '17 at 18:59
  • Hmm, so it wouldn't change much. Maybe lazy loading :) Anyway, for me SQLite-Net Extensions is OK for such simple operations like I perform. – Dawid Sibiński May 19 '17 at 20:03
0

If anyone wants to see more details on creating many-to-many relationships using SQLite-Net Extensions library, also containing tips and solutions provided here by @Shawn Kendrot, I've collected everything and wrote a post on my blog.

Hope it helps :)

Dawid Sibiński
  • 1,657
  • 3
  • 21
  • 40