Adding Linq method with column rename results to viewModel - c#

I have no problems passing Linq query results to views via a viewmodel with or without a .Select() method as long as I am selecting only a single column. When I tried using the .Select() option with a renamed column like this:
var custodians = _custodian.Contacts
.Where(c => !(c.personid.StartsWith("RMR") || c.personid.StartsWith("GMS")))
.Select(c => new { c.contactid, name = c.lname + ", " + c.fname})
.ToList();
it creates creates a System.Collections.Generic.List<<>f__AnonymousType1<int, string>> type list
I have an existing viewModel that I am passing to my view:
public class AssetViewModel
{
public string PsgcTagNumber { get; set; }
public string[] AssetAttributes { get; set; }
public string Message { get; set; }
public Asset Asset { get; set; }
public Location Location { get; set; }
public string Custodian { get; set; }
public ?????? AllContacts { get; set; }
}
What I cant figure out is the datatype to use for the AllContacts property of the viewModel.
Anyone point me in the right direction?

You'll need to define a class.
public class Contact {
public int contactid {get;set;}
public string name {get;set;}
}
.Select(c => new Contact { contactid = c.contactid, name = c.lname + ", " + c.fname})
public Contact[] AllContacts { get; set; }
Or just leave the entity alone, without doing a Select method on your query, and use it in your viewmodel - you could add a FormattedName property or something like that to handle your name.

Your anonymous type result is exactly what your select is producing new { c.contactid, name = c.lname + ", " + c.fname} - a list of int<->string or a list of { int contactid, string name }
If you want to use an existing Model, like your AssetViewModel.AllContacts you need to define its type first, as #Joe Enos stated and then update your query a little bit:
var vm = new AssetViewModel
{
PsgcTagNumber =...,
...,
Custodian =...,
AllContacts = _custodian.Contacts
.Where(c => !(c.personid.StartsWith("RMR") || c.personid.StartsWith("GMS")))
.Select(c => new Contact { c.contactid, name = c.lname + ", " + c.fname})
.ToList();
}
So then you have it: your view model, initiated and ready to be passed forward

Related

Dapper Multi Mapping Not Splitting on Named Parameters

New to Dapper here! Having an issue with multi-mapping. This is my query:
var sql = #"select distinct a.*,
c.Id as 'GenreId', c.Active as 'GenreActive', c.Link as 'GenreLink', c.Name as 'GenreName', c.DateCreated as 'GenreDateCreated', c.DateEdited as 'GenreDateEdited',
d.Id as 'CommentId', d.ReviewId as 'CommentReviewId', d.Name as 'CommentName', d.Email as 'Comment.Email', d.Content as 'CommentContent', d.Active as 'CommentActive', d.DateCreated as 'CommentDateCreated', d.DateEdited as 'CommentDateEdited', d.CommentId as 'ReplyCommentId'
from Review a " +
"left join ReviewGenre b on a.Id = b.ReviewId " +
"left join Genre c on c.Id = b.ReviewId " +
"left join Comment d on a.Id = d.ReviewId " +
"where a.Active = 1 " +
"order by a.DatePublished desc;"
;
And my entities are (shortened for brevity):
public class Review
{
public int Id {get;set;}
public IEnumerable<Genre> Genres { get; set; }
public IEnumerable<Comment> Comments { get; set; }
}
public class Genre
{
public int Id {get;set;}
public string Name {get;set;}
}
public class Comment
{
public int Id {get;set;}
public int Content {get;set;
}
My query using Dapper tries to split on the renamed columns for Genre.Id and Comment.Id. The query appears to be working fine except none of the Genres and Comments appear to be mapping to the Review class. This is how I am trying to execute the query:
using (var connection = new SqlConnection(_ConnectionString))
{
var reviews = await connection.QueryAsync<Review, Genre, Comment, Review>(
sql,
(review, genre, comment) =>
{
review.Genres = new List<Genre>();
review.Comments = new List<Comment>();
if (genre != null)
{
review.Genres.ToList().Add(genre);
}
if (comment != null)
{
review.Comments.ToList().Add(comment);
}
return review;
},
commandType: CommandType.Text,
splitOn: "GenreId,CommentId");
return reviews;
}
I have researched throughout tutorials and SO on the subject and not finding what could be causing the mapping to not happen.
I would appreciate any suggestions (newbie to Dapper). Thanks!
At this line
review.Genres.ToList().Add(genre);
you are creating each time a new list (.ToList()). This method returns/creates new instance, but the new instance is never assigned back to the model property. It's like doing something like that:
var list = new List<int>();
new List<int>().Add(1);
The two instances are separate objects.
What you can do is to changed your models to work like this (the lists are instantiated with the creation of the object):
public class Review
{
public int Id {get;set;}
public List<Genre> Genres { get; set; } = new List<Genre>();
public List<Comment> Comments { get; set; } = new List<Comment>();
}
and then adding elements like this:
review.Genres.Add(genre);
Or you can check the original dapper tutorial where they are using dictionary as state manager to remove duplicates.

Dynamically use DisplayName as alias in Linq query from entity

I have Manufacturer entity as below:
class Manufacturer
{
[Key]
public int Id { get; set; }
[DisplayName("Manufacturer Code")]
public int Code { get; set; }
[DisplayName("Manufacturer Name")]
public string Name { get; set; }
}
As you see every filed have DisplayName data annotations. In Normal way I select the rows of Manufacturer table by below code:
dataGridView1.DataSource = DatabaseContext.Set<Manufacturer>()
.Select(m => new
{
Id = m.Id,
Code = m.Code,
Name = m.Name,
})
.ToList();
I want to find a way that Dynamically put DisplayName as alias in Linq query.
I think I must a Method that generate the query something like:
dataGridView1.DataSource = DatabaseContext.Set<Manufacturer>()
.Select(m => new
{
Id = m.Id,
[Manufacturer Code] = m.Code,
[Manufacturer Name] = m.Name,
})
.ToList();
I could get the all DisplayName by below code:
public static Dictionary<string, string> GetEntityDisplayName(this Type type)
{
return TypeDescriptor.GetProperties(type)
.Cast<PropertyDescriptor>()
.ToDictionary(p => p.Name, p => p.DisplayName);
}
But dont know how do that. Is there any way to put DisplayName as alias of Linq query dynamically?
Update:
As one of answer say when use .ToList in get rows from an entity it return a list that project the model with DisplayNameAttribute, But the new thing is When create Linq query that use 2 entity, to list project the row that you exactly say in query. For example:
class Manufacturer
{
[Key]
public int Id { get; set; }
[DisplayName("Manufacturer Code")]
public int Code { get; set; }
[DisplayName("Manufacturer Name")]
public string Name { get; set; }
}
And:
class Good
{
[Key]
[DisplayName("ID")]
public int Id { get; set; }
[DisplayName("Good Name")]
public string Name { get; set; }
public int? ManufacturerId { get; set; }
public virtual Manufacturer Manufacturer { get; set; }
}
And The Query:
using (AppDbContext db = new AppDbContext())
{
var res2 = db.Goods.Select(m => new
{
Id = m.Id,
GoodsName = m.Name,
ManufacturerName = m.Manufacturer.Name,
}).ToList();
dataGridView1.DataSource = res2;
}
As you see in this case, because the query have 2 name field, must declare different alias for them and its not the equal to DisplayNameAttribute in entity. Are anyone know a way to project the output list same as DisplayNameAttribute defined in entity?
After looking at the DataGridView source, I've seen that the datasource is bound to a collection of objects that have properties with DisplayNameAttribute on them it will display those attributes values in the column header of that property.
So, you only need to project your LINQ queries to view models or entities that have defined the attribute.
For example, I have the following code:
var listOfEntities = new List<CustomEntity>
{
new CustomEntity {Id = 1, Name = "Name1", Value = "Value1"},
new CustomEntity {Id = 2, Name = "Name2", Value = "Value2"},
new CustomEntity {Id = 3, Name = "Name3", Value = "Value3"},
new CustomEntity {Id = 4, Name = "Name4", Value = "Value4"},
};
dataGridView1.DataSource = listOfEntities;
public class CustomEntity
{
public int Id { get; set; }
[DisplayName("Custom name header")]
public string Name { get; set; }
[DisplayName("Custom name value")]
public string Value { get; set; }
}
If you run this you will see that the column headers are those from the DisplayNameAttribute, not the properties name.
To project to view models you can use AutoMapper for automatic mapping of fields.
UPDATE
You can probably achieve this behavior using an Expando object.
Let me warn you though you can not use this until you call .ToList() on your context. Because this will not translate by default to a valid SQL query.
using System.Reflection;
/*...*/
public static object ToDynamicDisplayName(object input)
{
var type = input.GetType();
dynamic dObject = new ExpandoObject();
var dDict = (IDictionary<string, object>)dObject;
foreach (var p in type.GetProperties())
{
var prop = type.GetProperty(p.Name);
var displayNameAttr = p.GetCustomAttribute<DisplayNameAttribute>(false);
if (prop == null || prop.GetIndexParameters().Length != 0) continue;
if (displayNameAttr != null)
{
dDict[displayNameAttr.DisplayName] = prop.GetValue(input, null);
}
else
dDict[p.Name] = prop.GetValue(input, null);
}
return dObject;
}
To use it try it like this:
var myDynamicCollection = DatabaseContext.Set<Manufacturer>().ToList().Select(m =>
ToDynamicDisplayName(m));
UPDATE 2
Of course I've ran the code and built successfully. My set-up is VS2017 with .NET 4.6.2. But, I've also created a .Net Fiddle for it, it's available here.

How to insert complex array object if not exists using MongoDB C# Driver

I am trying to insert an object to an array property of my document only if the array doesn't contain another object with the same key. However I could not find the correct filter that does this using the C# driver. Details are below. Can you help me build the filter?
Here are my models
public class Document : Entity
{
public string Id { get; set; }
public string Name { get; set; }
public List<DocumentSubject> Subjects { get; set; }
...
}
public class DocumentSubject
{
public string Id { get; set; }
public DocumentSubjectType Type { get; set; }
public bool CanOpenIssue { get; set; }
...
}
Here is what I did so far (of course it's not complete)
var filter = Filter.And(
Filter.Eq(x => x.Id, id),
"PUT SOME FILTER FOR ARRAY ITEM EXISTENCE CHECK BY ID"
);
var updater = Updater.AddToSet(x => x.Subjects, subject);
var u =Collection.UpdateOne(filter, updater);
You can try below query.
The below query will use $elemMatch to check DocumentSubject array for element with id.
var queryBuilder = Builders<Document>.Filter;
var elemMatchBuilder = Builders<DocumentSubject>.Filter;
var filter = queryBuilder.Eq(document => document.Id, id) & queryBuilder.ElemMatch(document => document.Subjects, elemMatchBuilder.Ne(document => document.Id, subjectId));
var updater = Updater.Push(x => x.Subjects, subject);
var u = collection.UpdateOne(filter, updater);

How to query only specific properties of RealmObject?

For example, I have object:
public class person : RealmObject {
public string firstname { get; set; }
public string secondname { get; set; }
public int age { get; set; }
public string address { get; set; }
}
How to make query that will give me only list of addresses or firstnames + secondnames?
Or firstname and age.
In general Select is not currently supported...
As long as your Select is projecting a RealmObject, it is supported, but you are asking for a projection that changes the type from RealmObject to another type and that is not support:
https://github.com/realm/realm-dotnet/issues/916
If you really need to break object's connection to the Realm database, you can take your base query to a list (.ToList) and then perform a Select projection on the result:
realm.All<Dog>().ToList().Select(dog => dog.Name);
Update:
For your first + second name question, I would add that as a read-only property on your RealmObject subclass. Since that property has a custom setter/getter it will not be persisted in the Realm data store.
public class person : RealmObject
{
public string firstname { get; set; }
public string secondname { get; set; }
public int age { get; set; }
public string address { get; set; }
public string name { get { return firstname + " " + secondname; } }
}
Then you can do something like:
var realm = Realms.Realm.GetInstance("stackoverflow.db");
var me = new person() { firstname = "Sushi", secondname = "Hangover", age = 99, address = "Local sushi bar" };
realm.Write(() => realm.Add<person>(me));
var user = realm.All<person>().Where((person p) => p.firstname == "Sushi" && p.secondname == "Hangover").FirstOrDefault();
Console.WriteLine($"{user.name} is {user.age} years old and is currently at {user.address}");
Output:
Sushi Hangover is 99 years old and is currently at Local sushi bar
Update2:
If you are looking to iterate through a returned collection of RealmObjects:
var users = realm.All<person>().Where((person p) => p.age < 40);
Console.WriteLine($"There are {users.Count()} users under 40 years of age and they are located at:");
foreach (var user in users)
{
Console.WriteLine($"{user.firstname} is currently at {user.address}");
}
You could try something like the below:
var realm = Realm.GetInstance();
var addresses = realm.All<person>()
.ToList()
.Select(person => person.address)
.ToList();
var firstAndSecondNames = realm.All<person>()
.ToList()
.Select(person => new
{
FirstName = person.firstName,
SecondName = person.secondName
})
.ToList();

How to write LINQ query that selects a property which is a string concat of a collection of a nested entity properties?

Trying to be more efficient with my queries, but not sure how to write this all as one query. I've got a domain model:
public class UserReport : Entity
{
public string Name { get; set; }
public List<string> Statuses { get; set; }
public List<GroupModel> Groups { get; set; }
public string Email { get; set; }
public string OfficeStates {get; set;} //Comma delimited list
}
public class GroupModel : Entity
{
public string Name {get; set;}
public string Type {get; set;
}
Which is a "compound entity", if you will. The standard entities representing those collections are M2M relational entities with the User object:
public class User : Entity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<Status> Statuses { get; set; }
public ICollection<Group> Groups { get; set; }
public ICollection<Office> Offices { get; set; }
}
public class Office : Entity
{
//other properties
public State State { get; set; }
}
public class State : Entity
{
public string Name { get; set; }
public string Abbreviation { get; set; }
}
So far I've got:
Context.DbSet<User>.Select(user => new UserReport()
{
Name = user.FirstName + ", " + user.LastName,
Email = user.Email,
Statuses = user.Statuses.Select(status => status.Name).ToList(),
Groups = user.Groups.Select(group => new GroupModel(){ Name = group.Name, Type = group.Type.Name}).ToList(),
OfficeStates = string.Join(",", user.Offices.Select(office => office.State.Abbreviation).ToList())
}).ToList();
Which throws an error:
LINQ to Entities does not recognize the method 'System.String Join(System.String, System.Collections.Generic.IEnumerable`1[System.String])' method, and this method cannot be translated into a store expression.
I totally get what this is saying and I know if I called .ToList() before my select it would work, but I need to have this be one query, so I can still have an IQueryable to apply filtering later. Surely there must be some way in LINQ to do this. It can be done in SQL: Concatenate many rows into a single text string?, how can it be done via LINQ?
You can see that even in pure SQL that is not trivial (for xml, declaring variables etc). As far as I know there is no way to do exactly what you want with pure LINQ. However, at least in some cases you can do that using one query, though this query will not be the same as you would do this yourself in pure SQL. In your case that would be something like this:
Context.DbSet<User>.Select(user => new // note, anonymous type here
{
Name = user.FirstName + ", " + user.LastName,
Email = user.Email,
Statuses = user.Statuses.Select(status => status.Name), // no ToList - this won't work
Groups = user.Groups.Select(group => new GroupModel(){ Name = group.Name, Type = group.Type.Name}), // no ToList()
OfficeStates = user.Offices.Select(office => office.State.Abbreviation) // no ToList(), no String.Join()
}).ToList() // here we materizized, and now we can concatenate strings by hand
.Select(c => new UserReport {
Name = c.Name,
Email = c.Email,
Statuses = c.Statuses.ToList(),
Groups = c.Groups.ToList(),
OfficeStates = String.Join(",", c.Offices)
});
In simple cases I tested on, this generates one query to database, and this query receives only columns you need (though as I said - generated query is not the same you would use in SQL, conceptually). So I suggest to try this approach and see what query is generated (note also my comments in code above).

Categories

Resources