Entity Framework Include/select only certain properties from different tables - c#

Let's say I have a method called GetThreadWithComments(). Each thread has 1 user (the creator) and has a list of comments. Each comments has 1 user (the poster).
Here are the classes (generated by EF):
public class Thread
{
public int ThreadId { get; set; }
public int UserId { get; set; }
public string Message { get; set; }
public List<Comment> Comments { get; set; }
public User User { get; set; }
}
public class Comment
{
public long CommentId { get; set; }
public string Message { get; set; }
public int UserId { get; set; }
public int ThreadId { get; set; }
public User User { get; set; }
}
So basically, I want to load a thread with user info, and associated comments with user info. I've tried something like this:
db.Threads.Select(x => new
{
x,
x.User = new { x.User.Username, x.User.Email },
x.Comments = x.Comments.Select(c => new
{
c.Message,
c.CommentId,
c.User = new { c.User.Username, c.User.Email }
})
});
The above does not work. However, I am not too sure on how to correctly do this. I could use include, but that would generate all properties. Since speed is a concern, I am trying to keep things as light as possible.
Reason it does not work: it does not build. Compile time error. The 2 errors I get are:
Cannot implicitly convert type '' to...
and
CS0746 Invalid anonymous type member declarator. Anonymous type members must be declared with a member assignment, simple name or member access.

First, define entity relationships as virtual, for example
public User User { get; set; }
should be
public virtual User User { get; set; }
Second, in case of the later posted compiler error, try adding the member names.
So instead of
x.User = new { x.User.Username, x.User.Email }
use
x.User = new { Username = x.User.Username, Email = x.User.Email }
Also there is too much x in there. The corrected example would be:
db.Threads.Select(x => new
{
x,
User = new { Username = x.User.Username, Email = x.User.Email },
Comments = x.Comments.Select(c => new
{
c.Message,
c.CommentId,
User = new { Username = c.User.Username, Email = c.User.Email }
})
});

Try this,
var result = db.Threads.Include(thread => thread.Comments);
Hope helps,

Related

SQLite-Net-Extensions GetWithChildren() Method only reaches first Child Layer

currently I am working with the NuGet SQLite-Net-Extensions in Xamarin Forms and I have encountered a problem for which I can't find a solution.
The Problem: When calling GetWithChildren(primaryKey, recursive: true), the returned object only contains the first child layer. An example can be seen in the following.
The database is built up like this:
The equivalent code to this model is provided in the following:
User
namespace DatabaseTest
{
[Table("Users")]
public class User
{
[PrimaryKey,AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
[OneToMany]
public List<Contact> Contacts { get; set; }
}
}
Contact
namespace DatabaseTest
{
[Table("Contacts")]
public class Contact
{
[PrimaryKey,AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
[OneToMany]
public List<Entry> Entries { get; set; }
[ForeignKey(typeof(User))]
public int UserID { get; set; }
[ManyToOne]
public User User { get; set; }
}
}
Entry
namespace DatabaseTest
{
[Table("Entries")]
public class Entry
{
[PrimaryKey,AutoIncrement]
public int Id { get; set; }
public float Amount { get; set; }
[ForeignKey(typeof(Contact))]
public int ContactId { get; set; }
[ManyToOne]
public Contact Contact { get; set; }
}
}
In my App.cs I am creating the database and use the CreateTable() Method for all three classes. For the sake of this example, in MainPage.xaml.cs there is simply a button, which has a ButtonClicked Method.
In the real Application a process could look like this:
User logs in --> Adds Contact --> At some Point User creates Entry to one of his contacts
To accomplish this procedure in my example, the ButtonClicked Method looks like this:
void ButtonClicked(object sender, EventArgs e)
{
try
{
User user = new User()
{
Name = "Test user"
};
Contact contact = new Contact()
{
Name = "First contact"
};
Entry entry1 = new Entry()
{
Amount = 10F
};
Entry entry2 = new Entry()
{
Amount = 20F
};
App.Database.Insert(user);
if (user.Contacts==null)
{
user.Contacts = new List<Contact>();
}
App.Database.Insert(contact);
user.Contacts.Add(contact);
App.Database.UpdateWithChildren(user);
if (contact.Entries==null)
{
contact.Entries = new List<Entry>();
}
App.Database.Insert(entry1);
App.Database.Insert(entry2);
contact.Entries.Add(entry1);
contact.Entries.Add(entry2);
App.Database.UpdateWithChildren(contact);
App.Database.UpdateWithChildren(user);
var test = App.Database.GetWithChildren<User>(user.Id, recursive: true);
var test2 = App.Database.GetAllWithChildren<Contact>();
var test3 = App.Database.GetAllWithChildren<Entry>();
} catch (Exception ex)
{
Debug.Print(ex.Message);
}
}
I have set a breakpoint to the bracket that closes the try to inspect the result. In the End, my user looks like this:
Which is absolutely perfect.
However, when I try to get this user from my database, the result looks like this:
I don't know how to resolve this error and hope anyone can help me with this problem.
Since this post is very long already, I thank everyone who read this far in advance.
After many tries I finally solved my problem on my own.
Solution:
It could not have been any easier. In my User, Contact and Entry Class I provided my OneToMany and ManyToOne attributes with the CascadeOperation attribute. Example:
[OneToMany(CascadeOperations = CascadeOperation.All)]
public List<Entry> Entries { get; set; }
[ManyToOne(CascadeOperations = CascadeOperation.All)]
public User User { get; set; }
Even though I marked my GetWithChildren() Method as recursive: true, only by applying CascadeOperations it will work properly. More information about SQLite-Net-Extensions and CascadeOperations can be found here:
Source: TwinCoders SQLite-Net-Extensions

Iteration error in Entity Framework

I'm creating a simple Messaging application with C# Winforms. I'm connecting to a SQLEXPRESS server running on my computer and storing everything there.
Here is my schema:
public class UserContext : DbContext {
public UserContext() : base("name=BuddyDatabase") {
}
public DbSet<User> Users { get; set; }
public DbSet<Message> Messages { get; set; }
}
public class User {
[Key]
public string username { get; set; }
public string password { get; set; }
public static implicit operator User(bool v) {
throw new NotImplementedException();
}
public virtual List<User> friends { get; set; }
}
public class Message {
[Key]
public int ID { get; set; }
public virtual User sender { get; set; }
public virtual User recipient { get; set; }
public string content { get; set; }
public virtual List<User> group { get; set; }
}
Pretty simple
Sending messages to a single recipient works but group messaging doesn't, whenever I open the "Messages Screen" I get this error:
Unable to create a constant value of type 'WindowsFormsApp1.User'. Only primitive types or enumeration types are supported in this context.
And this is the method I have run when the Message Screen loads up:
var x = db.Messages.Where(b => b.recipient.username == currentuser.username);
foreach (var y in x) {
MainMessagesBox.Text += y.content;
}
x = null;
var z = db.Messages.Where(b => b.group.Contains(currentuser));
foreach (var y in z) {
MainMessagesBox.Text += y.content;
}
(Visual Studio highlights the 'in' in this line as the cause of the error.)
foreach (var y in z) {
Thought this might be a problem with me using the Where method and involving non-primitives (as the error message suggests) so I tried changing my schema so that group is a list of strings that contain the usernames of the intended recipients and modified the methods accordingly but that didn't work either. Will provide code and errors for that trial on request.
Here is the actual "Send Message" code:
if (!textBox1.Text.Contains(',')) {
db.Messages.Add(new Message { sender = currentuser, recipient = db.Users.Find(textBox1.Text), content = currentuser.username + ": " + ContentBox.Text + "\n" });
db.SaveChanges();
} else {
List<User> recips = new List<User>();
string[] poop = textBox1.Text.Split(',');
foreach (var x in poop) {
recips.Add(db.Users.Find(x));
}
db.Messages.Add(new Message { sender = currentuser, group = recips, content = ContentBox.Text });
db.SaveChanges();
}
poop is an array of the intended recipients separated by comma from a textbox, this is temporary.
Sorry if anything is misformatted or I'm unclear, this is my first question.
Thank you in advance.
The problem is the Select query on message table where you check for the current user to be in the group list. This won't work, because the query has to be translated to SQL to be send to the database.
SQL does not understand what a ´User´ type is and can't compare references. And actually, neither does C# compare this properly. Two objects of type ´User´ with the same username would not be equal in your case. You need to compare the username of those objects.
Regardless, as Bitmask explained in a comment, you have to properly define the foreign key for the messages.
It's quite easy for the sender of the messages. You have a public virtual ICollection<Message> Messages { get; set; } in your User class.
But for the recipients, it's a many-to-many relation. So both, the User class and the Message class have a collection to Message and User respectively.
Something like this should work:
public class User {
[Key]
public string username { get; set; }
public string password { get; set; }
public static implicit operator User(bool v) {
throw new NotImplementedException();
}
public virtual ICollection<Message> SentMessages { get; set; }
public virtual ICollection<Message> ReceivedMessages { get; set; }
public virtual List<User> friends { get; set; }
}
public class Message {
[Key]
public int ID { get; set; }
public virtual User Sender { get; set; }
public virtual ICollection<User> Recipients { get; set; }
public string content { get; set; }
}
Check this link for an example on how to define a many-to-many relation either using data annotations or fluent API. This assumes you're using EF6 though. If you're using an older version of EF, you might have to define the joining table yourself to get the many-to-many relation.
And as for the query. You can use the following in your message screen OnLoad method:
MainMessagesBox.Text = string.Join(System.Environment.NewLine, currentuser.ReceivedMessages.Select(m => m.content))
This concatenates all messages, separated with a new line.

C# Copy List items to Object Arrays

I have a list created from a stored procedure using EF6.0
I have also created 3 classes
public class Resas
{
public string todo{ get; set; }
public string prop { get; set; }
public string Code { get; set; }
public string statusCode { get; set; }
public string checkin { get; set; }
public string checkout { get; set; }
public List<profiles> profiles { get; set; }
}
public class profiles
{
public string action { get; set; }
public string id { get; set; }
public string profileType { get; set; }
public string title { get; set; }
public string firstName { get; set; }
public string middleName { get; set; }
public string lastName { get; set; }
public List<emailAddresses> emailAdresses { get; set; }
}
public class emailAddresses
{
public string emailAddress { get; set; }
public string emailAddress2 { get; set; }
}
I am doing a for-loop in the list and I need to get certain columns and put it in the array (I will put two, to keep it simple)
myEntities db = new myEntities();
List<rev_Result> revList = new List<rev_Result>();
revList.Clear();
revList = db.rev().ToList();
for (int i = 0; i < revList.Count(); i++)
{
Resas resas = new Resas();
profiles[] profiles = new profiles[1];
resas.todo = revList[i].todo;
resas.profiles[0].lastName = revList[i].lastName;
}
I am not familiar with C# as you can see from the psedo-code above.
I cannot figure out how to feed the Resas with data and then its Profile with data and then move to the next Resas entry.
Any help appreciated.
That's fairly simple using Linq:
Resas resas = new Resas();
resas.profiles = revList
.Select(x => new profiles() { action = x.todo, lastName = x.lastName })
.ToList();
What's happening here is: You loop through every entry in revList and get your wanted data structure (that's what Select is doing). x refers to the current entry in the loop, while the stuff to the right side of the arrow is you 'output': a new instance of your profiles class with the members assigned accordingly. The result of all of this is then converted to a list (before ToList(), think of it as a recipe to create the list) and assigned to resas.profiles.
By the way, a word on conventions: Usually, in C#, you would give your classes a name that starts with a capital letter. Also, your profiles class seems to contain data of exactly one profile, so a better name might be Profile. This also makes your data structure more clear, since List<profiles> seems to be a list of lists of profiles - but that's not what it actually is, is it?
Furthermore, Members generally start with a capital letter as well, so instead of action, lastName, you'd have: Action and LastName.
You can try with Linq. This is the code that should solve your issue, but Resas class doesn't have action property:
List<Resas> ls = revList.Select(x => new Resas() {
action = x.todo,
profiles = new List<profiles>() {
new profiles { lastName = x.lastName }
}
).ToList();
If you need to use action property of inprofiles` class:
List<Resas> ls = revList.Select(x => new Resas() {
profiles = new List<profiles>() {
new profiles {
action = x.todo,
lastName = x.lastName
}
}
).ToList();

Error inserting record with entity framework

I am sorry if it has already been answered but I can't find any solution. Here is my (little) problem. Also all my apologies if the terms I use are approximate, I am far from being a skilled C# developer
Note that I think my problem is similar to this one Entity Framework validation error for missing field, but it's not missing?
I have a table "Tweets" with a tweet_id field (bigint) which is my primary key.
I use the following class to load the table :
class TwitterDbContext : DbContext
{
public TwitterDbContext() : base("Twitter")
{
}
public DbSet<Stream> Streams { get; set; }
public DbSet<StreamParameter> StreamParameters { get; set; }
public DbSet<Tweet> Tweets { get; set; }
}
public class Tweet
{
public Tweet()
{
}
[Key]
public long tweet_id { get; set; }
public string tweet { get; set; }
public long creator { get; set; }
public double latitude { get; set; }
public double longitude { get; set; }
public string language { get; set; }
public DateTime created_at { get; set; }
public DateTime registered_at { get; set; }
public long? in_reply_to { get; set; }
public bool retweeted { get; set; }
}
I have an other class to store within the code execution all the fields used by the Tweet table. For the need here, let's imagine I manually create it that way
private void Test_Button_Click(object sender, RoutedEventArgs e)
{
Twts twtReceived = new Twts();
twtReceived.tweet_id = 1;
twtReceived.tweet = "test";
twtReceived.creator = 1;
twtReceived.latitude = -1;
twtReceived.longitude = -1;
twtReceived.language = "a";
twtReceived.created_at = DateTime.Now;
twtReceived.registered_at = DateTime.Now;
twtReceived.in_reply_to = 1;
twtReceived.retweeted = true;
AddTweet(twtReceived);
}
Now here is the AddTweet method
static public void AddTweet(Twts twtReceived)
{
try
{
// update the tweet data in the database
using (var TwitterDb = new TwitterDbContext())
{
Tweet twt = new Tweet()
{
tweet_id = twtReceived.tweet_id,
tweet = twtReceived.tweet,
creator = twtReceived.creator,
longitude = twtReceived.longitude,
latitude = twtReceived.latitude,
language = twtReceived.language,
created_at = twtReceived.created_at,
registered_at = twtReceived.registered_at,
in_reply_to = twtReceived.in_reply_to,
retweeted = twtReceived.retweeted
};
TwitterDb.Tweets.Add(twt);
TwitterDb.SaveChanges();
}
}
catch(Exception ex)
{
MessageBox.Show(ex.InnerException.ToString());
}
}
I constantly have the same error message:
Cannot insert the value NULL into column 'tweet_id', table
'Twitter.dbo.Tweets'; column does not allow nulls. INSERT fails.
The thing is that when I spy on "TwitterDb.Tweets.Local" after TwitterDb.Tweets.Add(twt); I correctly have tweet_id set to 1.
Any idea where is the issue?
Try marking your tweet_id field with following (instead of just [Key]), if this is a primary key column where you want to provide values yourself
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
If it is an auto-increment, then remove explicit assignments to this field and mark it as 'Identity' instead:
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]

AutoMapper not updating a nullable long? to null

I have the following two classes:
public class DomainStudent {
public long Id { get; set; }
public string AdvisorId { get; set; }
public long? DegreeId { get; set; }
}
public class ApiStudent {
public long Id { get; set; }
public long AdvisorName { get; set; }
public long? DegreeId { get; set; }
}
When I run the following mapping:
Mapper.CreateMap<ApiStudent, DomainStudent>();
var api = new ApiStudent();
api.Id = 123;
api.AdvisorName = "Homer Simpson";
api.DegreeId = null;
var domain = new DomainStudent();
domain.Id = 123;
domain.AdvisorName = "Marge Simpson";
domain.DegreeId = 5; // i want this to get written to null
Mapper.Map(api, domain);
// at this point domain.DegreeId = 5 instead of null
I would have thought this worked by default. Am I missing something?
By default automapper will ignore null source values.
You can change this with the following:
Mapper.Initialize( Conf =>
{
Conf.ForSourceType<ApiStudent>().AllowNullDestinationValues = true;
} );
Otherwise you can try:
Mapper.CreateMap<long?, long?>()
.ConvertUsing(v => v);
Pretty ugly hack to have to do something like this but it might work.
edit: Just for clarity I wanted to note the final solution to the question was upgrading to AutoMapper version 3.2.1

Categories

Resources