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.
Related
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
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,
I have .net 4.5.2 test app playing about with Azure Mobile Services and I'm attempting to store data using the TableController. I have my data types as follows:
public class Run:EntityData
{
public int RunId { get; set; }
public DateTime? ActivityStarted { get; set; }
public DateTime? ActivityCompleted { get; set; }
public List<Lap> LapInformation { get; set; }
public Run()
{
LapInformation = new List<Lap>();
}
}
public class Lap
{
[Key]
public int LapNumber { get; set; }
public int CaloriesBurnt { get; set; }
public double Distance {get; set;}
//Some other basic fields in here
public DateTime? LapActivityStarted { get; set; }
public DateTime? LapActivityCompleted { get; set; }
public Lap()
{
}
In my Startup class I call:
HttpConfiguration config = new HttpConfiguration();
new MobileAppConfiguration()
.UseDefaultConfiguration()
.ApplyTo(config);
And in my MobileServiceContext class:
public class MobileServiceContext : DbContext
{
private const string connectionStringName = "Name=MS_TableConnectionString2";
public MobileServiceContext() : base(connectionStringName)
{
}
public DbSet<Run> Runs { get; set; }
public DbSet<Lap> Laps { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add(
new AttributeToColumnAnnotationConvention<TableColumnAttribute, string>(
"ServiceTableColumn", (property, attributes) => attributes.Single().ColumnType.ToString()));
}
}
In my controller then, I have:
[MobileAppController]
public class RunController: TableController<Run>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
MobileServiceContext context = new MobileServiceContext();
DomainManager = new EntityDomainManager<Run>(context, Request);
}
public IList<Run> GetAllRuns()
{
var runs = context.Runs.Include("LapInformation").ToList();
return runs;
}
public SingleResult<Run> GetRun(string id)
{
return Lookup(id);
}
public async Task<IHttpActionResult> PostRun(Run run)
{
Run current = await InsertAsync(run);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
public Task DeleteRun(string id)
{
return DeleteAsync(id);
}
}
I can then POST a record in fiddler which responds with a 201 and the Location of the newly created Item. An Example of the data I'm posting is:
{RunId: 1234, LapInformation:[{LapNumber:1,Distance:0.8, LapActivityStarted: "2017-06-19T00:00:00", LapActivityCompleted: "2017-06-19T00:00:00", CaloriesBurnt: 12}]}
However, when I GET that object, I'm only getting the fields from Run, without the list of Detail records (Lap). Is there anything I have to configure in Entity Framework so that when I GET a Run record from the DB, it also gets and deserializes all associated detail records?
Hopefully that makes sense.
EDIT
Turns out that it is pulling back all the lap information, but when I return it to the client, that information is getting lost.
You can use custom EF query with Include() method instead of Lookup call preferably overload that takes function from System.Data.Entity namespace.
var runs = context.Runs.Include(r => r.LapInformation)
Take a look at https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx
AFAIK, you could also use the $expand parameter to expand your collections as follows:
GET /tables/Run$expand=LapInformation
Here is my sample, you could refer to it:
You could mark your action with a custom ActionFilterAttribute for automatically adding the $expand property to your query request as follows:
// GET tables/TodoItem
[ExpandProperty("Tags")]
public IQueryable<TodoItem> GetAllTodoItems()
{
return Query();
}
For more details, you could refer to adrian hall's book chapter3 relationships.
EDIT Turns out that it is pulling back all the lap information, but when I return it to the client, that information is getting lost.
I defined the following models in my mobile client:
public class TodoItem
{
public string Id { get; set; }
public string UserId { get; set; }
public string Text { get; set; }
public List<Tag> Tags { get; set; }
}
public class Tag
{
public string Id { get; set; }
public string TagName { get; set; }
}
After execute the following pull operation, I could retrieve the tags as follows:
await todoTable.PullAsync("todoItems", todoTable.CreateQuery());
Note: The Tags data is read-only, you could only update the information in the ToDoItem table.
Additionally, as adrian hall mentioned in Data Access and Offline Sync - The Domain Manager:
I prefer handling tables individually and handling relationship management on the mobile client manually. This causes more code on the mobile client but makes the server much simpler by avoiding most of the complexity of relationships.
This drives me crazy I have mode with list of two other models in it. As long as I had there iCollections it worked fine, but I had to change them to List, because I need to export the data into XML.
Model
public class PortalUser
{
//private List<UserToTeam> UserToTeam_ = new List<UserToTeam>();
//private List<Mandays> Mandays_ = new List<Mandays>();
public int ID { get; set;}
public string Name { get; set; }
[DisplayName("Last Name")]
public string LastName { get; set; }
public Role Role { get; set; }
//public virtual ICollection<Mandays> Mandays { get; set; }
public List<Mandays> Mandays { get; set; } //{ return Mandays_; }
[DisplayName("Team")]
//public virtual ICollection<UserToTeam> UserToTeam { get; set; }
public List<UserToTeam> UserToTeam { get; set; } //{ return UserToTeam_; }
public PortalUser()
{
Mandays = Mandays ?? new List<Mandays>();
UserToTeam = UserToTeam ?? new List<UserToTeam>();
}
Then I use this to get data from the DB
List<PortalUser> dbAgent = db.PortalUser.ToList();
End while debugging the result ends as Count = 0. While iCollection return the model within model correctly the List always returns Count = 0. In DB Users have Mandays and even team, but result is always empty.
I am sure I am missing something simple, please help. I can post more code if needed.
I am not sure if EF supports a List collection like that, however you need the virtual keyword in there so EF can create wrapped collections appropriately.
Got a question. I get this error and I know it is due to the fact that int32 has a number limit of 2147483647. But I don't know why I am getting this error when the value in question (a telephone number of 11 digits) is defined as a string in our SQL database, a string in our web service and a string in our web application.
I assume it is something to do with the way the service serialises and deserialises data over a connection, but I was wanting to know if there is a way to force Number to use only the string instead of parsing it when deserialisation happens. Or even get it to parse as int64.
Here is the error exception. I removed the namespace and service name. It is the property Number that is causing the problem.
There was an error deserializing the object of type .".ClientPhone[]. The value '07721545554' cannot be parsed as the type 'Int32'."
And here is the code for the service and the service interface.
[DataContract]
public class ClientPhone
{
[DataMember]
public int? ClientNumberID { get; set; }
[DataMember]
public int? RefID { get; set; }
[DataMember]
public string Number { get; set; }
[DataMember]
public string NumberType { get; set; }
[DataMember]
public bool? PrimaryNumber { get; set; }
}
public partial class ClientNumberEntity
{
public int ClientNumbersID { get; set; }
public Nullable<int> RefID { get; set; }
public string ClientNumberType { get; set; }
public string ClientNumber { get; set; }
public Nullable<bool> PrimaryNumber { get; set; }
public virtual ClientDataEntity ClientData { get; set; }
}
public List<ClientPhone> GetClientsPhoneByReference(int _reference)
{
OurDatabaseEntities context = new OurDatabaseEntities();
var phoneEntity = (from c in context.ClientNumberEntities
where c.RefID == _reference
select c).ToList();
if (phoneEntity != null)
{
return TranslateClientPhoneEntityToPhoneNumberList(phoneEntity);
}
else
throw new Exception("Unable to get phone data");
}
private List<ClientPhone> TranslateClientPhoneEntityToPhoneNumberList(List<ClientNumberEntity> numberEntities)
{
List<ClientPhone> phoneList = new List<ClientPhone>();
foreach (ClientNumberEntity numberEntity in numberEntities)
{
ClientPhone phoneListMember = new ClientPhone();
phoneListMember.ClientNumberID = numberEntity.ClientNumbersID;
phoneListMember.RefID = numberEntity.RefID;
phoneListMember.Number = numberEntity.ClientNumber;
phoneListMember.NumberType = numberEntity.ClientNumberType;
phoneListMember.PrimaryNumber = numberEntity.PrimaryNumber;
phoneList.Add(phoneListMember);
}
return phoneList;
}
Any advice on a solution would be greatly appreciated! Thanks :)
Got a solution, albeit it's more stupidity on my end.
I didn't realise that my .EDMX entity diagram hadn't been updated with the new values from the database (I had to manually delete the entity and re-add it to force changes).
After re-compiling and updating the service reference, it worked.