Unable to query child types using marten and Linq - linq

I am new to Marten and am having terrible difficulty with what should be an easy query. I have a Person class that has a property of type EmailAddress. I want to find any person with a specific email address.
public class EmailAddress : ValueObject
{
// construction
protected EmailAddress() { } // needed for deserialization
public EmailAddress(EmailType type, string emailAddress)
{
Guard.Against.Null(type, nameof(type));
Guard.Against.NullOrWhiteSpace(emailAddress, nameof(emailAddress));
Guard.Against.NotValidEmail(emailAddress, nameof(emailAddress));
if (!(type.HasFlag(EmailType.Personal) || type.HasFlag(EmailType.Work) || type.HasFlag(EmailType.Other))) throw new ArgumentException("Unable to craete an EmailAddress without a valid type (i.e., Personal, Work, Other).");
Type = type;
Address = emailAddress;
}
// properties
public EmailType Type { get; private set; }
public string Address { get; private set; }
}
public class Person : ValueObject
{
// fields
List<EmailAddress> _emails = new List<EmailAddress>();
// Construction
protected Person() { } // needed for deserialization
public Person(Guid id, string firstName, string lastName, EmailAddress identityAddress)
{
Guard.Against.Default(id, nameof(id));
Guard.Against.NullOrWhiteSpace(firstName, nameof(firstName));
Guard.Against.NullOrWhiteSpace(lastName, nameof(lastName));
Guard.Against.Null(identityAddress, nameof(identityAddress));
if (!identityAddress.Type.HasFlag(EmailType.IsIdentity))
{
identityAddress = new EmailAddress(identityAddress.Type | EmailType.IsIdentity, identityAddress.Address);
}
this.Id = id;
this.FirstName = firstName;
this.LastName = lastName;
_emails.Add(identityAddress);
}
// properties
public Guid Id { get; private set; }
public string FirstName { get; private set; }
public string LastName { get; private set; }
public IReadOnlyList<EmailAddress> Emails { get => _emails; }
}
I have created a test class that is wired up to PostgreSQl, and am able to insert types using Marten. The serializer is working well and is able to serialize and deserialize these types using their non-public setters. But any queries related to the Emails property on the Person type fail.
[TestMethod]
public void Can_Find_Person_Manual()
{
// set the test values
Guid lukeId = Guid.NewGuid();
EmailAddress lukeEmail = new EmailAddress(EmailType.Work | EmailType.IsIdentity, "luke#skywalkerranch.com");
Person lukeSkywalker = new Person(lukeId, "Luke", "Skywalker", lukeEmail);
Guid leiaId = Guid.NewGuid();
EmailAddress leiaEmail = new EmailAddress(EmailType.Personal | EmailType.IsIdentity, "leia#skywalkerranch.com");
Person leiaSolo = new Person(leiaId, "Leia", "Solo", leiaEmail);
// add a test object
IDocumentStore database = _container.GetInstance<IDocumentStore>();
using (var session = database.LightweightSession())
{
// clear prior people
session.DeleteWhere<Person>(p => true);
session.SaveChanges();
// Add new people
session.Store<Person>(lukeSkywalker);
session.Store<Person>(leiaSolo);
session.SaveChanges();
// Start Testing
IReadOnlyList<Person> people = session.LoadMany<Person>(new Guid[] { lukeId, leiaId });
Assert.IsTrue(people.Count == 2);
Person luke = session.Load<Person>(lukeId);
Assert.IsTrue(luke.Id == lukeId);
Assert.IsTrue(luke.Emails.Contains(e => e.Address == "luke#skywalkerranch.com"));
Person foundLuke = session.Query<Person>().Where(p => p.FirstName == "Luke").First();
Assert.IsTrue(foundLuke.Id == lukeId);
Person leia = session.Load<Person>(leiaId);
Assert.IsTrue(leia.Id == leiaId);
Assert.IsTrue(leia.Emails.Contains(e => e.Address == "leia#skywalkerranch.com"));
List<Person> allPeople = session.Query<Person>().ToList(); // works fine 2 items
List<Person> allLeias = session.Query<Person>().Where(p => p.FirstName == "Leia").ToList(); // works fine 1 item
List<Person> allBills = session.Query<Person>().Where(p => p.FirstName == "Bill").ToList(); // works fine 0 items
// List<Person> withEmail = session.Query<Person>().Where(p => p.Emails.Count > 0).ToList(); // ERROR returns empty list
// List<Person> withEmail = session.Query<Person>().Where(p => p.Emails.Count(e => true) > 0).ToList(); // ERROR: select d.data, d.id, d.mt_version from public.mt_doc_person as d where jsonb_array_length(CAST(d.data ->> 'Emails' as jsonb)) > :arg0$ $ 22023: cannot get array length of a non - array'
List<Person> allLukes = session.Query<Person>().Where(p => p.Emails.Any(e => e.Address == "luke#skywalkerranch.com")).ToList(); // ERROR NullreferenceException
//// should get Leia
List<Person> withPersonalEmail = session.Query<Person>().Where(p => p.Emails.Any(e => e.Type == EmailType.Personal)).ToList(); // ERROR returns empty List
List<Person> ranchEmail = session.Query<Person>().Where(p => p.Emails.Any(e => e.Address.Contains("ranch"))).ToList(); // ERROR 'Specific method not supported'
// Below is the one is need
Person foundLeia = session.Query<Person>().Where(p => p.Emails.Any(_ => _.Address == "leia#skywalkerranch.com")).SingleOrDefault(); // ERROR returns null
Assert.IsTrue(foundLeia.Id == leiaId);
}
}

In our case, the issue was that our collection properties weren't being serialised as JSON arrays.
We solved this by setting the CollectionStorage property of the Json serialiser to
CollectionStorage.AsArray, so collections are serialised as JSON arrays.
See the 'Collection Storage' section at this link: https://martendb.io/documentation/documents/json/newtonsoft/
We also needed to set TypeNameHandling to None, to prevent type metadata from being stored when our collections were serialised and saved to the database. (See the introductory section of the above link for more info.)
var serialiser = new JsonNetSerializer
{
CollectionStorage = CollectionStorage.AsArray,
EnumStorage = EnumStorage.AsString
};
serialiser.Customize(x =>
{
x.TypeNameHandling = TypeNameHandling.None;
});

Related

linq distinct with object selection

I have the following linq statement:
consumers = data.Select(x => new Consumer()
{
firstname = x.firstname,
lastname = x.lastname,
house = x.sublocationid,
floornr = x.floor,
appnr = x.roomnr
})
.Distinct()
.ToList();
Somehow this does not return distinct datasets. I assume it has something to do with the selection of the object? The distinct function is therefore not comparing the attributes directly but rather the objects? I am not understanding it fully unfortunately but in ms sql this statement works fine.
I also tried the following but it does not return a List object and I would need to use var or something else and I need a List of Consumer() objects.
consumers = data.Select(x => new Consumer()
{
firstname = x.firstname,
lastname = x.lastname,
house = x.sublocationid,
floornr = x.floor,
appnr = x.roomnr
})
.GroupBy(x => new { x.firstname, x.lastname, x.haus, x.etage, x.appnr })
.ToList();
You can do/try this:
public class Consumer
{
public string firstname { get; set; }
public string lastname { get; set; }
public string floor { get; set; }
public string house { get; set; }
}
List<Consumer> objConsumer = new List<Consumer>()
{
new Consumer(){ firstname="Govind", lastname="Sahu", house="298",floor="1st Floor"},
new Consumer(){ firstname="Govind", lastname="Sahu", house="298",floor="1st Floor"},
new Consumer(){ firstname="Govind1", lastname="Sahoo", house="297",floor="1st Floor"}
};
1st approach:
var obj = objConsumer.GroupBy(s => new {
s.firstname,
s.lastname,
s.house,
s.floor,
}).Select(o=>o.FirstOrDefault()).ToList();
2nd approach:
var obj1 = objConsumer.Select(s => new { firstname = s.firstname, lastname = s.lastname, house = s.house, floor = s.floor }).Distinct().ToList();
I found another solution by using DistinctBy from MoreLinq. It returns the right result when I use it like this
consumers = data.Select(x => new Consumer()
{
firstname = x.firstname,
lastname = x.lastname,
house = x.sublocationid,
floor = x.floor,
appnr = x.roomnr
})
.DistinctBy(x=> new
{
x.firstname,
x.lastname,
x.floor,
x.appnr,
x.house
})
.ToList();

"Operation is not valid due to the current state of the object." Exception, when I want to retrieve Items

I use this method to retrieve Items for a Tree component in Blazor Serverside, In the DAL I have:
public List<TreeItem> GetTreeItems()
{
var tree = new List<TreeItem>();
TreeItem item = new TreeItem()
{
DepartmentID = 0,
CategoryID = 0,
Text = "Root",
Childs = context.Departments.OrderBy(d => d.Order).Select(d => new TreeItem()
{
DepartmentID = d.Id,
CategoryID = 0,
Text = d.Title,
Childs = d.Categories.OrderBy(c => c.Order).Select(c => new TreeItem()
{
DepartmentID = d.Id,
CategoryID = c.Id,
Text = c.Title
}).ToList()
}).ToList()
};
tree.Add(item);
return tree;
}
The TreeItem class is the following, (The model shared by the blazor Component and Dal Class):
public class TreeItem
{
public int DepartmentID { get; set; }
public int CategoryID { get; set; }
public string Text { get; set; }
public List<TreeItem> Childs { get; set; }
}
But when I was to retrieve Items for the tree in the blazor component I get the exception: Operation is not valid due to the current state of the object., admin is the DAL class I inject to Blazor component as follows:
private void GetTreeModel()
{
try
{
Items = admin.GetTreeItems();
TreeSuccess = true;
TreeMessage = "Success";
return;
}
catch (Exception ex) // Error here
{
TreeSuccess = false;
TreeMessage = "Can not load tree items";
return;
}
}
What is this error and How to solve it?
I solved my problem using First loading entities and then using Linq to Objects, Like this:
var tree = new List<TreeItem>();
var departments = context.Departments.OrderBy(d => d.Order).ToList();
var categories = context.Categories.OrderBy(c => c.Order).ToList();
TreeItem item = new TreeItem()
{
DepartmentID = 0,
CategoryID = 0,
Text = "Root",
Childs = departments.Select(d => new TreeItem()
{
DepartmentID = d.Id,
CategoryID = 0,
Text = d.Title,
Childs = categories.Where(c => c.DepartmentID == d.Id).OrderBy(c => c.Order).Select(c => new TreeItem()
{
DepartmentID = d.Id,
CategoryID = c.Id,
Text = c.Title
}).ToList()
}).ToList()
};
tree.Add(item);
return tree;
}

Entity Framework Code First and populating join tables

I been practicing with EF Code First, SQL Express, and ASP.Net MVC3.
When I run the website first the correct tables are generated by the FooInitializer and Student and Image are populated but for some reason the join table (StudentImages) is not being populated.
What could be the issue?
Tables: Student, Image, and StudentImages
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Image> Images { get; set; }
}
public class Image
{
public int Id { get; set; }
public string Filename { get; set; }
public string Extension { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
public class FooInitializer : DropCreateDatabaseIfModelChanges<DBContext>
{
protected override void Seed(DBContext context)
{
var students = new List<Student> {
new Student { Id = 1, Name = "John" },
new Student { Id = 2, Name = "Jane" }
};
students.ForEach(s => context.Students.Add(s));
context.SaveChanges();
var images = new List<Image> {
new Image { Id = 1, Filename = "IMG_4596.JPG", Extension = ".jpg" },
new Image { Id = 2, Filename = "IMG_4600.JPG", Extension = ".jpg" }
};
images.ForEach(i => context.Images.Add(i));
students[0].Images.Add(images[0]);
context.SaveChanges();
}
}
From what I can tell your Image class does not have a reference to the StudentID. Try adding:
public int StudentID { get; set; }
to the Image class maybe?
Also having an ICollection would mean that one image could have multiple students - is this correct? Maybe it should be a public virtual Student Student {...}
EDIT: Also I found this, with a many to many relationship (if thats what you need):
In your OnModelCreating() Method:
modelBuilder.Entity<Student>()
.HasMany(c => c.Images).WithMany(i => i.Students)
.Map(t => t.MapLeftKey("StudentId")
.MapRightKey("ImageID")
.ToTable("StudentImages"));
taken from this link that states:
A many-to-many relationship between the Instructor and Course
entities. The code specifies the table and column names for the join
table. Code First can configure the many-to-many relationship for you
without this code, but if you don't call it, you will get default
names such as InstructorInstructorID for the InstructorID column.
EDIT: Here is the code I used the other night, with my implementation of the code first MVC site:
var users = new List<User>
{
new User { UserID = new Guid(), Email = "me#me.com", LastOnline = DateTime.Now, Password = "pword", RegistrationDate = DateTime.Now, SecurityAnswer = "me", SecurityQuestion = "who?", Roles = new List<Role>() },
};
users.ForEach(s => context.Users.Add(s));
context.SaveChanges();
var roles = new List<Role>
{
new Role { RoleID = "Admin", Description = "Administration Users", Users = new List<User>() }
};
roles.ForEach(r => context.Roles.Add(r));
users[0].Roles.Add(roles[0]);
context.SaveChanges();
var userLicense = new List<UserLicense>
{
new UserLicense { AddDateTime = DateTime.Now, LicenseType = "Farmer", Manufacturer = "Motorola", Model = "Droid", PhoneIdentifier = "c0e4223a910f", UserID = users[0].UserID, User = new User() }
};
userLicense[0].User = users[0];
userLicense.ForEach(u => context.UserLicenses.Add(u));
context.SaveChanges();
userLicense[0].User = users[0];
context.SaveChanges();
Notice in each instantiated item, I am also instantiating a new referenced item within the parent object.
EDIT:
Ok try this:
var students = new List<Student> {
new Student { Id = 1, Name = "John", Images = new List<Image>() },
new Student { Id = 2, Name = "Jane", Images = new List<Image>() }
};
students.ForEach(s => context.Students.Add(s));
context.SaveChanges();
var images = new List<Image> {
new Image { Id = 1, Filename = "IMG_4596.JPG", Extension = ".jpg", Students = new List<Student>() },
new Image { Id = 2, Filename = "IMG_4600.JPG", Extension = ".jpg", Students = new List<Student>() }
};
images.ForEach(i => context.Images.Add(i));
students[0].Images.Add(images[0]);
students[1].Images.Add(images[1]);
context.SaveChanges();
Try adding this before saving changes for each student:
foreach (Image i in s1.Images)
context.ObjectStateManager.ChangeObjectState(i, System.Data.EntityState.Added);
Also try with System.Data.EntityState.Modified.
Hope this works...

Lambda statement to project results based on if

I have a user who is part of a company, this company can have multiple offices, so the user can be part of head/main office or part of another office, I am projecting from a lamdba expression but I cannot figure out how to do: if user is not headoffice throw out the office address.
The code below shows user, joined onto userhistory (2 table join - which is necessary so I can throw out some info that is held on that table related to this user), here is what I have done so far:
[HttpPost]
[AjaxOnly]
public PartialViewResult GetUsers(string term)
{
var _sf = _repo.Single<Company>(x => x.Type == x.IsActive &&
(x.Identifier.Contains(term) || x.Name.Contains(term)));
//get company with addresses...
var users = _repo.All<User>().Where(x => x.CompanyID == _sf.CompanyID);
var offices = _repo.All<Office>().Where(x => x.CompanyID == _sf.CompanyID);
var _users = _repo.All<UserHistory>()
.Join(users, x => x.UserID, y => y.UserID,
(s, u) => new
{
_s = s,
_user = u
}).Select(x => new QuoteData
{
Login = x._user.Login,
Name = string.Format("{0} {1}", x._user.FirstName, x._user.LastName),
Tel = x._s.Mobile,
//let me know where user is based, if head office get me the address too...
IsBasedInHeadOffice = x._user.IsBasedInHeadOffice
//here: if !IsBasedInHeadOffice => GetMeAddress
});
return PartialView("QuoteUsersUC", _users);
}
public class QuoteData
{
public string Login { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Tel { get; set; }
public bool IsBasedInHeadOffice { get; set; }
}
Could I have written this even better/ simpler?
You can do it like this:
.Select(x =>
{
var result = new QuoteData
{
Login = x._user.Login,
Name = string.Format("{0} {1}", x._user.FirstName,
x._user.LastName),
Tel = x._surveyor.Mobile,
IsBasedInHeadOffice = x._user.IsBasedInHeadOffice
};
if(!result.IsBasedInHeadOffice)
result.Address = GetMeAddress();
return result;
});
UPDATE:
Using LINQ2SQL or EF, this should be a lot simpler because of the so called navigation properties. Basically, they remove the need for manual joins in your C# code, if your database is properly set up with foreign keys.
For example, if your table USER_HISTORY would have a foreign key constraint on the column user_id to the table USER, your class User would have a property UserHistories of type IEnumerable<UserHistory> that contains all associated user histories and the class UserHistory would have a property User. The same is true for all other associations: User <-> Company and Company <-> Office
Using this, your code could easily be rewritten to this:
public PartialViewResult GetUsers(string term)
{
var sf = _repo.Single<Company>(x => x.Type == x.IsActive &&
(x.Identifier.Contains(term) || x.Name.Contains(term)));
var users =
sf.Users
.SelectMany(x => x.UserHistories
.Select(y =>
new QuoteData
{
Login = x.Login,
Name = string.Format("{0} {1}",
x.FirstName,
x.LastName),
Tel = y.Mobile,
IsBasedInHeadOffice = x.IsBasedInHeadOffice
Address = x.IsBasedInHeadOffice ?
sf.Office.Address :
string.Empty
}));
return PartialView("QuoteUsersUC", _users);
}

Returning LINQ to Entities Query Result as JSON string

I'm attempting to construct a web service that allows for RESTful requests to return LINQ to Entities data as JSON string data. I have no problem executing a call to the database that returns one specific object:
public Product GetTicket(string s)
{
int id = Convert.ToInt32(s);
MWAEntities context = new MWAEntities();
var ticketEntity = (from p
in context.HD_TicketCurrentStatus
where p.Ticket_ID == id
select p).FirstOrDefault();
if (ticketEntity != null)
return TranslateTicketEntityToTicket(ticketEntity);
else
throw new Exception("Invalid Ticket ID");
/**
Product product = new Product();
product.TicketId = 1;
product.TicketDescription = "MyTest";
product.TicketOperator = "Chad Cross";
product.TicketStatus = "Work in Progress";
return product;
*/
}
private Product TranslateTicketEntityToTicket(
HD_TicketCurrentStatus ticketEntity)
{
Product ticket = new Product();
ticket.TicketId = ticketEntity.Ticket_ID;
ticket.TicketDescription = ticketEntity.F_PrivateMessage;
ticket.TicketStatus = ticketEntity.L_Status;
ticket.TicketOperator = ticketEntity.L_Technician;
return ticket;
}
Using curl, I get json data:
curl http://192.168.210.129:1111/ProductService/ticket/2
{"TicketDescription":"Firewall seems to be blocking her connection to www.rskco.com","TicketId":2,"TicketOperator":"Jeff","TicketStatus":"Completed"}
That being said, I have no idea how to get a string of JSON objects using the following query:
public List<MyTicket> GetMyTickets(string userId)
{
MWAEntities context = new MWAEntities();
/**
* List of statuses that I consider to be "open"
* */
string[] statusOpen = new string[] { "Work in Progress", "Assigned", "Unassigned" };
/**
* List of tickets with my userID
* */
var tickets = (from p
in context.HD_TicketCurrentStatus
where statusOpen.Contains(p.L_Status) & p.L_Technician == userId
select new MyTicket(p.Ticket_ID, p.Ticket_CrtdUser, p.F_PrivateMessage, p.Ticket_CrtdDate, p.L_Status));
return ???;
}
MyTicket is a type defined as follows:
[DataContract]
public class MyTicket
{
public MyTicket(int ticketId, string TicketCreator, string FirstPrivateMessage, DateTime TicketCreatedDate, string Status)
{
this.TicketId = ticketId;
this.TicketCreator = TicketCreator;
this.FirstPrivateMessage = FirstPrivateMessage;
this.TicketCreatedDate = TicketCreatedDate;
this.Status = Status;
}
[DataMember]
public int TicketId { get; set; }
[DataMember]
public string TicketCreator { get; set; }
[DataMember]
public string FirstPrivateMessage { get; set; }
[DataMember]
public DateTime TicketCreatedDate { get; set; }
[DataMember]
public string Status { get; set; }
//p.Ticket_CrtdUser, p.Ticket_CrtdDate, p.Ticket_ID, p.F_PrivateMessage
}
I would just like to get a list of JSON strings as output in order to parse using JS. I've tried using a foreach loop to parse "var" into a List of MyTicket objects, calling .ToList()), etc., to no avail.
I cannot change the backend (SQL 2005/2008), but I'm trying to use a standard HTML/JS client to consume a .NET 4.0 web service. Any help would be greatly appreciated. I've spent literally days searching and reading books (especially on O'Reilly's Safari site) and I have not found a reasonable solution :(.
use Json.NET: http://james.newtonking.com/pages/json-net.aspx
using Newtonsoft.Json;
var serializer = new JsonSerializer();
serializer.Serialize(Response.Output, tickets); // per your example
EDIT: Argh, the above is if you want to handle the serialization yourself.
In your example, change the return of the method from List to Ticket[] and do
return tickets.ToArray();
I wanted to add that I eventually got help to solve this. I'm not using business entities even though I'm using the Entity Framework. This may not be a wise decision, but I'm increasingly confused with Linq2SQL and Linq2EF. Here is the the code that made the above work:
public List<MyTicket> GetMyTickets(string userId)
{
MWAEntities context = new MWAEntities();
/**
* List of statuses that I consider to be "open"
* */
string[] statusOpen = new string[] { "Work in Progress", "Created"};
var tickets = (from p
in context.HD_TicketCurrentStatus
where statusOpen.Contains(p.L_Status) & p.L_Technician == userId
select new MyTicket{
TicketId = p.Ticket_ID,
TicketCreatedDate = p.Ticket_CrtdDate,
FirstPrivateMessage = p.F_PrivateMessage,
Status = p.L_Status,
TicketCreator = p.Ticket_CrtdUser
});
return tickets.ToList();
}

Resources