Lambda statement to project results based on if - linq

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);
}

Related

Linq - Join collection with child collection of other collection

I have a collection of users, each with a child collection of memberships. And then I also have a club collection. The child collection of each user is populated with memberships containing a club ID only. What I want is for each membership to also contain the club entity matching the club ID.
My entities:
public class User
{
public string Name { get; set; }
public IEnumerable<Membership> Memberships { get; set; }
}
public class Membership
{
public Guid ClubId { get; set; }
public Club Club { get; set; }
}
public class Club
{
public Guid Id { get; set; }
public string Name { get; set; }
}
Populated with data:
var club1Id = new Guid("FFEB77B9-1616-463B-B36E-F95F7E255FDE");
var club2Id = new Guid("7A6ECD38-5AEB-418B-9DD5-FBD777CE190C");
var users = new List<User>
{
new User
{
Name = "Patrick Stewart",
Memberships = new List<Membership>
{
new Membership { ClubId = club1Id },
new Membership { ClubId = club2Id }
}
},
new User
{
Name = "Brent Spiner",
Memberships = new List<Membership>
{
new Membership { ClubId = club1Id }
}
},
new User
{
Name = "Wesley Crusher",
Memberships = null
}
};
var clubs = new List<Club>
{
new Club
{
Id = club1Id,
Name = "Officers Club"
},
new Club
{
Id = club2Id,
Name = "Captains Club"
}
};
And then I need a LINQ query that adds the matched Club objects to the Membership objects.
So that when I print out users like this:
foreach (var user in users)
{
Console.WriteLine($"User: {user.Name}");
if (user.Memberships != null)
{
foreach (var membership in user.Memberships)
{
Console.WriteLine($" Membership: {membership.Club?.Name}");
}
}
}
It will look like this:
User: Patrick Stewart
Membership: Officers Club
Membership: Captains Club
User: Brent Spiner
Membership: Officers Club
User: Wesley Crusher
I want to do it in one go with a LINQ expression, so that I don't have to involve for-loops.
I ended up solving it with this LINQ expression:
users = users.Select(user => new {
user,
Memberships = user.Memberships?
.GroupJoin(clubs, membership => membership.ClubId, club => club.Id, (membership, club) => new { membership, club})
.SelectMany(mc => mc.club.DefaultIfEmpty(), (mc, club) =>
{
mc.membership.Club = club;
return mc.membership;
})
})
.Select(x =>
{
x.user.Memberships = x.Memberships;
return x.user;
})
.ToList();
LINQ is not intended to be used for modifying data, rather querying data and/or creating new data from data.
Instead, use a nested foreach loop.
First, create a Dictionary to make finding Club objects easier:
var clubDict = clubs.ToDictionary(c => c.Id);
Then, use the clubDict to modify each Membership to include the matching Club entity:
foreach (var user in users.Where(u => u.Memberships != null))
foreach (var membership in user.Memberships)
membership.Club = clubDict[membership.ClubId];

Unable to query child types using marten and 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;
});

Select multiple columns in LINQ

I've written a LINQ query shown below :
List<Actions> actions = resourceActions.Actions.Select(s => s.ActionName).ToList();
How do I give for selecting multiple columns here ? ie I want to add columns s.ActionId and s.IsActive. I'm unable to apply it.
Make a class to represent the data you want:
public class ResourceAction
{
public int Id {get;set;}
public string Name {get; set; }
}
Select a list of those instead:
List<ResourceAction> actions = resourceActions.Actions
.Select(s => new ResourceAction() { Id = s.Id, Name = s.ActionName}).ToList();
I believe this is what your looking for. However you need to change the output to an anonymous type.
var actions = resourceActions.Actions.Select(s => new { s.ActionName, s.ActionId, s.IsActive } ).ToList();
You can use a anonymous type for this, for example
var actions = resourceActions.Actions.Select(s =>
new { Id = s.Id, Name = s.ActionName, Active = s.IsActive).ToList();
but a better way would be to create a class like
public class ActionWithId
{
public int Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
}
List<ActionWithId> actions = resourceActions.Actions.Select(s =>
new ActionWithId() { Id = s.Id, Name = s.ActionName, Active = s.IsActive }).ToList();

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...

How do I add a where filter using the original Linq-to-SQL object in the following scenario

I am performing a select query using the following Linq expression:
Table<Tbl_Movement> movements = context.Tbl_Movement;
var query = from m in movements
select new MovementSummary
{
Id = m.DocketId,
Created = m.DateTimeStamp,
CreatedBy = m.Tbl_User.FullName,
DocketNumber = m.DocketNumber,
DocketTypeDescription = m.Ref_DocketType.DocketType,
DocketTypeId = m.DocketTypeId,
Site = new Site()
{
Id = m.Tbl_Site.SiteId,
FirstLine = m.Tbl_Site.FirstLine,
Postcode = m.Tbl_Site.Postcode,
SiteName = m.Tbl_Site.SiteName,
TownCity = m.Tbl_Site.TownCity,
Brewery = new Brewery()
{
Id = m.Tbl_Site.Ref_Brewery.BreweryId,
BreweryName = m.Tbl_Site.Ref_Brewery.BreweryName
},
Region = new Region()
{
Description = m.Tbl_Site.Ref_Region.Description,
Id = m.Tbl_Site.Ref_Region.RegionId
}
}
};
I am also passing in an IFilter class into the method where this select is performed.
public interface IJobFilter
{
int? PersonId { get; set; }
int? RegionId { get; set; }
int? SiteId { get; set; }
int? AssetId { get; set; }
}
How do I add these where parameters into my SQL expression? Preferably I'd like this done in another method as the filtering will be re-used across multiple repositories.
Unfortunately when I do query.Where it has become an IQueryable<MovementSummary>. I'm assuming it has become this as I'm returning an IEnumerable<MovementSummary>. I've only just started learning LINQ, so be gentle.
Answer:
private IQueryable<Tbl_Docket> BuildQuery(IQueryable<Tbl_Docket> movements, IMovementFilter filter)
{
if (filter != null)
{
if (filter.PersonId.HasValue) movements = movements.Where(m => m.UserId == filter.PersonId);
if (filter.SiteId.HasValue) ...
}
return movements;
}
Which is called like follows:
var query = from m in this.BuildQuery(movements, filter)
select new... {}
You have to call the where statement before you fire your select statement, e.g.:
IQueryable<Tbl_Movement> movements = context.Tbl_Movement;
if (filter != null)
{
if (filter.PersonId != null) movements = movements.Where(m => m....PersonId == filter.PersonId);
if (filter.RegionId != null) movements = movements.Where(m => m....RegionId == filter.RegionId);
if (filter.SiteId != null) movements = movements.Where(m => m...SiteId == filter.SiteId);
if (filter.AssetId != null) movements = movements.Where(m => m...AssetId == filter.AssetId);
}
var query = m from movements...
As opposed to using this IFilter class, you might want to consider a Fluent Pipe-based Repository structure, e.g.:
var movements = new MovementsPipe()
.FindSiteId(1)
.FindAssetIds(1, 2, 3)
.FindRegionId(m => m > 10)
.ToMovementSummaryList();
Hope this helps. Let me know if you have any questions.

Resources