I am facing an issue in using SQLite-Net Extensions to save data in local DB in scenario where the foreign key is referencing the same entity (self-join).
Example – Employee and Manager. Every employee has a manager and a manager is also an employee. I am facing issues in saving data in such cases. It will be really helpful if you can provide some insights. Does this extension support this kind of relationship?
Yes, relationships between objects of the same class are supported, but the foreign keys and inverse properties must be explicitly specified in the relationship property attribute because the discovery system will get it wrong as there are be two relationships with the same type.
This example is extracted from the project readme:
public class TwitterUser {
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
[ManyToMany(typeof(FollowerLeaderRelationshipTable), "LeaderId", "Followers",
CascadeOperations = CascadeOperation.CascadeRead)]
public List<TwitterUser> FollowingUsers { get; set; }
// ReadOnly is required because we're not specifying the followers manually, but want to obtain them from database
[ManyToMany(typeof(FollowerLeaderRelationshipTable), "FollowerId", "FollowingUsers",
CascadeOperations = CascadeOperation.CascadeRead, ReadOnly = true)]
public List<TwitterUser> Followers { get; set; }
}
// Intermediate class, not used directly anywhere in the code, only in ManyToMany attributes and table creation
public class FollowerLeaderRelationshipTable {
public int LeaderId { get; set; }
public int FollowerId { get; set; }
}
As you can see here we have a many-to-many between Twitter users. In your case it will be a one-to-many, so you won't need the intermediate table and you'll need the foreign key (ManagerId for example) in your Person class.
Related
I have a problem with an SQLite database in my Xamarin.Forms PCL project.
I have followed this example from Microsoft Docs:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/databases
I've been using my own types to store data and it's worked Ok for simple custom types, but I've recently added List<int> and Attendance type to the custom object (Info).
Now when I try and create the object, i get the following errors:
Don't know about System.Collections.Generic.List`1[System.Int32]
Don't know about MyApp.Attendance
Here is the init code:
readonly SQLiteAsyncConnection database;
database = new SQLiteAsyncConnection(dbPath);
database.CreateTableAsync<UserPrefrences>().Wait();
database.CreateTableAsync<Attendance>().Wait();
database.CreateTableAsync<Info>().Wait();
I'm using Xamarin.Forms with Xamarin.iOS.
You can not store them by default like that. However there is sqlite-net-extensions which you can use to accomplish that. You can take a look about sqlite-net-extensions here.
Using this extension you will be able to do that with TextBlob property, something like this:
public class Address
{
public string StreetName { get; set; }
public string Number { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
}
public class Person
{
public string Name { get; set; }
[TextBlob("PhonesBlobbed")]
public List<string> PhoneNumbers { get; set; }
[TextBlob("AddressesBlobbed")]
public List<Address> Addresses { get; set; }
public string PhonesBlobbed { get; set; } // serialized phone numbers
public string AddressesBlobbed { get; set; } // serialized addresses
}
More explanation about TextBlob from url.
Text blobbed properties Text-blobbed properties are serialized into a text property when saved and deserialized when loaded. This allows
storing simple objects in the same table in a single column.
Text-blobbed properties have a small overhead of serializing and
deserializing the objects and some limitations, but are the best way
to store simple objects like List or Dictionary of basic types or
simple relationships.
Text-blobbed properties require a declared string property where the
serialized object is stored.
I just saw that there is also similar/same questions about this topic on StackOverflow already, so you can take a look at them also.
How can you store lists of objects in SQLite.net?
Can I use a List of String in a class intended for SQLite?
The problem is i cannot perform cascade deletion using only EF codefirst conventions. They, in particular, say: "If a foreign key on the dependent entity is not nullable, then Code First sets cascade delete on the relationship"
I have parent and child entities:
[Table("AssociationPages")]
public class AssociationPage
{
[Column("AssociationPageID"), Required, Key]
public int Id { get; set; }
[Required, ForeignKey("AssociationSetting")]
public int AssociationId { get; set; }
public virtual AssociationSetting AssociationSetting { get; set; }
}
[Table("AssociationSetting")]
public class AssociationSetting
{
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public int AssociationId { get; set; }
public virtual ICollection<AssociationPage> Pages { get; set; }
}
My AssociationPages table in MS SQL Server looks like:
CREATE TABLE [dbo].[AssociationPages](
[AssociationPageID] [int] IDENTITY(1,1) NOT NULL,
[AssociationId] [int] NOT NULL,
...
)
and a FK (but it shouldnt matter as EF has its own conventions):
ALTER TABLE [dbo].[AssociationPages] WITH CHECK ADD CONSTRAINT [FK_ChamberPages_Chambers] FOREIGN KEY([AssociationId])
REFERENCES [dbo].[AssociationSetting] ([AssociationId])
GO
So i have non-nullable FK everywhere but once i try to delete parent AssociationSetting row then getting the "The DELETE statement conflicted with the REFERENCE constraint FK_ChamberPages_Chambers. The conflict occurred in database ..., table dbo.AssociationPages, column AssociationId message". I know i can set constraints inside database or with EF fluent API but why this is not working?
Thanks for your ideas!
update
WillCascadeOnDelete() doesnt work as well :(
It may be that Code First is not setting up the cascade since you are not following the naming conventions.
Try this:
public class AssociationPage
{
public int Id { get; set; }
public int AssociationSettingId { get; set; }
public virtual AssociationSetting AssociationSetting { get; set; }
}
public class AssociationSetting
{
public int Id { get; set; }
public virtual ICollection<AssociationPage> Pages { get; set; }
}
Okay, in investigation purposes i created a simpliest parent-child tables, put two rows - one per table, created FK relationship as "No Action" on cascade delete and wrote EF Code First entities.
Then I set up FK relationship variuos ways - via column attributes, Fluent API, explicitly specifying WillDeleteOnCascade() method or alltogether but had no luck trying to delete parent row. The only way I achieved this when retrieved both parent and child records prior to removing. At this point SQL profiler shown that rows being deleted one by one both for parent and children tables.
Summarizing the above I suppose the cascading in EF Code First means the setting constraints on the database being created by EF. I might be missing something thu.
I have two classes: Customer and Association.
A customer can have an association with many customers. Each association is of a defined type (Family, Friend, etc) i.e Customer A is a friend of Customer B. Customer A is related to Customer C. The type of association is defined by an enum AssociationType.
In order to create this in EF i've defined the following classes
public class Customer
{
public string FirstName {get; set;}
public string LastName {get; set;}
public virtual ICollection<Association> Associations { get; set; }
}
public class Association
{
public int CustomerId { get; set; }
public virtual Customer Customer { get; set; }
public int AssociatedCustomerId { get; set; }
public virtual Customer AssociatedCustomer { get; set; }
public AssociationType AssociationType { get; set; }
}
I've removed the Data Annotations as I was unable to get this to compile. I get the error:
"Model compatibility cannot be checked because the database does not
contain model metadata".
Does anyone have any ideas?
It happens sometimes when an error occurs during database creation. The database schema is created then - except the __MigrationHistory table. When you run your application again EF wants to check against the __MigrationHistory table if the schema is still up-to-date with the model and if that table doesn't exist it throws the exception you are having.
To fix the problem either delete the database manually or set the initializer to DropCreateDatabaseAlways<MyContext> (with Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>()) - only once. After the DB is created set it back to your original initializer.
BTW: For your model you will have to specify explicitly that Customer.Associations is related to Association.Customer, either with data annotations...
[InverseProperty("Customer")]
public virtual ICollection<Association> Associations { get; set; }
...or with Fluent API:
modelBuilder.Entity<Customer>()
.HasMany(c => c.Associations)
.WithRequired(a => a.Customer)
.HasForeignKey(a => a.CustomerId);
Thank you Slauma,
your answer got us going in the right direction.
We added the following configuration to the Association configuration:
HasRequired(x => x.AssociatedCustomer).WithMany().WillCascadeOnDelete(false);
We are using EF Code First 4.3.1.
We are developing an ASP.NET Web Role referring to multiple class libraries.
There are two class libraries each containing classes and an individual DBcontext.
Lets say the Library1 has classes A and B.
DBcon1: DbSet and DbSet
Lets say the Library2 has classes C and D.
Class C{
[Key]
public int CId{ get; set;}
[Required]
public virtual A referencedA {get; set;}
}
DBcon2: DbSet<C> and DbSet<D>
When I try to use the DBcon2 as such:
using (var con = new DBcon2())
{
C vr = new C();
vr.CId= 1;
vr.referencedA = DBCon1.As.First();
con.Cs.Add(vr);
con.SaveChanges();
}
I get an exception as:
"The member with identity does not exist in the metadata collection.
Parameter name: identity"
Both DBCon1 and DBcon2 are using the sane SQL Server Database "SampleDB".
Please point me in the right direction.
I got this error and fixed it by not trying to set the navigation property in the related table, just set the foreign key id instead
eg
public class Student()
{
public int StudentId { get; set; }
public string StudentName { get; set; }
public int CourseId { get; set; }
public virtual Course Course { get; set; }
}
public class Course()
{
public int CourseId { get; set; }
public string CourseName { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
Main code:
var myCourse = new Course();
var myCourseId = 1;
var student = new Student() {
CourseId = myCourseId
// Course = myCourse <-- this would cause the error
}
Might not be your issue but maybe it will point you in the right direction and hopefully will help someone else.
The exception is a bit cryptic, but pretty clear if you realise that a context needs information about entities (metadata) to be able to write sql statements. Thus, your DBcon2 context has no clue where to find the primary key of an A, because it has no metadata about A.
You could however set an integer property A_Id (or the like), but then you'll have to write custom code to resolve it to an A.
Another option is to merge (parts of) the contexts, if possible.
I have the following classes:
public class CartItem
{
public long Id { get; set; }
public int Quantity { get; set; }
public Product Product { get; set; }
}
public class Product {
public long Id { get; set; }
public string Title { get; set; }
public decimal Price { get; set; }
}
I currently have the following configuration:
modelBuilder.Entity<CartItem>().HasRequired(x => x.Product).WithMany().Map(x => x.MapKey("ProductId"));
I am trying to ensure that whenever I retrieve a cartitem from the database there will be a join on the product table so I can access the product properties but not the other way around.
I basically want to be able to do:
string title = cartItem.Product.Title
using the configuration I have gives me an Object reference not set to an instance of an object exception.
Short answer: to solve your problem, make the Product property virtual.
In-depth:
First, you don't need a join to do this. EF works fine with lazy loading (you need the virtual modifier)
Second, you can fetch the Product eagerly, using the Include extension method. Example:
var cartItem = context.CartItems.Include(x => x.Product)
.Where(/*some condition*/).ToList();
...but you can't configure this to be the default behavior (nor is it a good idea usually)
Third, this is a many-to-one relationship, not one-to-one (a Product has many related CartItems)