Joining Linq Expressions - linq

I'm working with the new EF4 CTP4 although I don't think this has much to do with that. I am trying to set up a system where I can add auditable fields for our database automatically. What I'm trying to do is combine the following two expressions
a => new
{
a.CreatedBy,
a.CreatedTime,
a.UpdatedBy,
a.UpdatedTime
}
and
a => new
{
a.Id,
a.Name,
}
so the result is equivalant to
a => new
{
a.Id,
a.Name,
a.CreatedBy,
a.CreatedTime,
a.UpdatedBy,
a.UpdatedTime
}
the result I need to be an Expression<Func<T, object>>. I've been poking around and tried several things with Expression.Invoke and Expression.And(andalso) and haven't found anything that is working for me.
I'm not quite sure if this is possible but any help would be appreciated.

I don't think you can simply 'merge' two expressions. But you can use alternate API to create mappings with EntityMap.
public static class MapBuilder
{
public static Expression<Func<T, object>> GetMap<T>(Expression<Func<T, object>> func) where T: IAuditable
{
var body = func.Body as NewExpression;
var param = Expression.Parameter(typeof(T), "o");
var propertyAccessExprs = new List<Expression>();
foreach (MemberInfo member in body.Members)
{
propertyAccessExprs.Add(Expression.Property(param, member.Name));
}
var props = typeof(IAuditable).GetProperties();
foreach (PropertyInfo prop in props)
{
propertyAccessExprs.Add(Expression.Property(param, prop.Name));
}
var columnMappins = new List<Expression>();
foreach (var access in propertyAccessExprs)
{
columnMappins.Add(Expression.Call(typeof(EntityMap).GetMethod("Column", new Type[] {typeof(Object)}), Expression.Convert(access, typeof(Object))));
}
var RowExpr = Expression.Call(typeof(EntityMap).GetMethod("Row"), Expression.NewArrayInit(typeof(EntityMapColumn), columnMappins));
var result = Expression.Lambda<Func<T, object>>(RowExpr, param);
return result;
}
}
The usage is
var builder = new ModelBuilder();
builder.Entity<SimpleAuditableObject>()
.HasKey(o => o.Id)
.MapSingleType(MapBuilder.GetMap<SimpleAuditableObject>(o => new { o.Id, o.Name }));
Where
public interface IAuditable
{
int CreatedBy { get; set; }
DateTime CreatedTime { get; set; }
int UpdatedBy { get; set; }
DateTime UpdatedTime { get; set; }
}
public class SimpleAuditableObject : IAuditable
{
public int Id { get; set; }
public string Name { get; set; }
public int CreatedBy { get; set; }
public DateTime CreatedTime { get; set; }
public int UpdatedBy { get; set; }
public DateTime UpdatedTime { get; set; }
}
HTH.

Related

Convert collections of database entities to collections of view models

I am working on a .NET Core Web API
So far I used to return anonymous types in my controllers but now I want to start using the full power of swagger with auto documentation of the return types.
Which lead me to start using view models.
But I am struggling with converting between the auto-generated database model classes
and the auto-generated swagger view model classes.
It works for a single instance (see GetPerson method in the controller below) but fails when I want to return lists.
So my questions:
How do I cast/convert collections/lists of objects between view models and database models
Is the code in the controller correct? Are there easier/shorter/better ways to do the conversion? (I read about using the implicit operator)
Error message I get:
Cannot implicitly convert type 'System.Linq.IQueryable' to 'System.Collections.Generic.List'. An explicit conversion exists (are you missing a cast?)
It gives me an InvalidCastException if I cast them explicitly like
List result = (List)_dbContext.Person....
there seems to be a problem with generics in the display of stackoverflow
Assume I used the generic lists with giving a type PersonView
My code looks like:
Database models
public partial class Person
{
public Person()
{
}
public int Id { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public int? MainAdressId { get; set; }
public virtual Adress MainAdress { get; set; }
}
public partial class Adress
{
public Adress()
{
Person = new HashSet();
}
public int Id { get; set; }
public string CityName { get; set; }
public int CityPostalCode { get; set; }
public string StreetName { get; set; }
public string HouseNumber { get; set; }
public string FloorNumber { get; set; }
public string DoorNumber { get; set; }
public virtual ICollection Person { get; set; }
}
View models
public class City
{
public string Name { get; set; }
public int PostalCode { get; set; }
}
public class Street
{
public string Name { get; set; }
public string HouseNumber { get; set; }
public string FloorNumber { get; set; }
public string DoorNumber { get; set; }
}
public class AdressView
{
public Street Street { get; set; }
public City City { get; set; }
}
public class PersonView
{
public string FirstName { get; set; }
public string Lastname { get; set; }
public AdressView Adress { get; set; }
}
The controller class which is working for a single instance but not for lists
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Swashbuckle.SwaggerGen.Annotations;
using PersonExample.ModelsPersonDB;
using PersonExample.ModelsViewPerson;
namespace PersonExample.Controllers
{
[Route("api/[controller]")]
public class PersonViewTestController : Controller
{
private readonly PersonDBContext _dbContext;
private readonly ILogger _logger;
public PersonViewTestController(PersonDBContext dbContext, ILogger logger)
{
_dbContext = dbContext;
_logger = logger;
_logger.LogDebug("{0} > new instance created", GetType().Name);
}
[HttpGet("{id:int}", Name = "GetPerson")]
[ProducesResponseType(typeof(PersonView), 200)]
[SwaggerOperation("GetPerson")]
public virtual IActionResult GetPerson([FromRoute]int id)
{
PersonView result = _dbContext.Person
.Include(p => p.MainAdress)
.Where(p => p.Id == id)
.Select(p => new PersonView()
{
FirstName = p.Firstname,
Lastname = p.Lastname,
Adress = (p.MainAdress == null) ? null :
new AdressView()
{
Street = new Street()
{
Name = p.MainAdress.StreetName,
HouseNumber = p.MainAdress.HouseNumber,
FloorNumber = p.MainAdress.FloorNumber,
DoorNumber = p.MainAdress.DoorNumber
},
City = new City()
{
Name = p.MainAdress.CityName,
PostalCode = p.MainAdress.CityPostalCode
}
}
}
)
.FirstOrDefault();
return new ObjectResult(result);
}
[HttpGet(Name = "GetPersonList")]
[ProducesResponseType(typeof(List), 200)]
[SwaggerOperation("GetPersonList")]
public virtual IActionResult GetPersonList()
{
List result = _dbContext.Person
.Include(p => p.MainAdress)
.Select(p => new PersonView()
{
FirstName = p.Firstname,
Lastname = p.Lastname,
Adress = (p.MainAdress == null) ? null :
new AdressView()
{
Street = new Street()
{
Name = p.MainAdress.StreetName,
HouseNumber = p.MainAdress.HouseNumber,
FloorNumber = p.MainAdress.FloorNumber,
DoorNumber = p.MainAdress.DoorNumber
},
City = new City()
{
Name = p.MainAdress.CityName,
PostalCode = p.MainAdress.CityPostalCode
}
}
}
);
return new ObjectResult(result);
}
}
}
you can use AutoMapper https://github.com/AutoMapper/AutoMapper/wiki/Getting-started
here some examples: Simple Automapper Example
example with EF core and ASP.NET WebApi: https://github.com/chsakell/aspnet5-angular2-typescript
I missed the .ToList() at the end of the query.
The full controller know looks like:
[HttpGet(Name = "GetPersonList")]
[ProducesResponseType(typeof(List), 200)]
[SwaggerOperation("GetPersonList")]
public virtual IActionResult GetPersonList()
{
List result = _dbContext.Person
.Include(p => p.MainAdress)
.Select(p => new PersonView()
{
FirstName = p.Firstname,
Lastname = p.Lastname,
Adress = (p.MainAdress == null) ? null :
new AdressView()
{
Street = new Street()
{
Name = p.MainAdress.StreetName,
HouseNumber = p.MainAdress.HouseNumber,
FloorNumber = p.MainAdress.FloorNumber,
DoorNumber = p.MainAdress.DoorNumber
},
City = new City()
{
Name = p.MainAdress.CityName,
PostalCode = p.MainAdress.CityPostalCode
}
}
}
).ToList(); //missed that line
return new ObjectResult(result);
}

Select Foreign key data in EF

I have 2 models : Account and Task
public class Account
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Key]
public Guid UserId { get; set; }
public string UserType { get; set; }
public string Name { get; set; }
}
public class Task
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int id { get; set; }
[Key]
public Guid TaskId { get; set; }
[ForeignKey("TaskSubType")]
public Guid TaskSubTypeId { get; set; }
public virtual TaskSubType TaskSubType { get; set; }
[ForeignKey("Account")]
public Guid TaskCreator { get; set; }
public virtual Account Account { get; set; }
}
I call getTasks:
public List<TaskOverViewViewModel> GetTasks()
{
IEnumerable<Task> result = db.Tasks.AsEnumerable();
List<TaskOverViewViewModel> list = ToViewModelMapper.toViewModel(result);
return list;
}
public class ToViewModelMapper
{
internal static List<TaskOverViewViewModel> toViewModel(IEnumerable<Task> entitys )
{
List<TaskOverViewViewModel> modelList = new List<TaskOverViewViewModel>();
foreach (var entity in entitys)
{
TaskOverViewViewModel model = new TaskOverViewViewModel();
model.SubTaskName = entity.TaskSubType.Name;
model.TaskCreator = entity.Account.LoginName;
model.ToolsAccesable = entity.ToolsAccesable;
modelList.Add(model);
}
return modelList;
}
}
But it fails because i entity.TaskSubType and entity.Account is null. But if i return IEnumerable<Task> result = db.Tasks.AsEnumerable(); in the call everything works fine and i can see that Json contains all TaskSubtype and Account models.
From linq-to-entities, you can project directly to your ViewModel class.
public List<TaskOverViewViewModel> GetTasks()
{
List<TaskOverViewViewModel> result = (from t in db.Tasks
select new TaskOverViewViewModel
{
SubTaskName = t.TaskSubType.Name;
TaskCreator = t.Account.LoginName;
ToolsAccesable = t.ToolsAccesable;
}).ToList();
return result;
}
If you prefer method syntax, you can use this:
List<TaskOverViewViewModel> result = db.Tasks.Select(t => new TaskOverViewViewModel
{
SubTaskName = t.TaskSubType.Name;
TaskCreator = t.Account.LoginName;
ToolsAccesable = t.ToolsAccesable;
}).ToList();

error in linq cannot convert from 'System.Collections.Generic.IEnumerable<AnonymousType#1>' to 'System.Linq.IQueryable<AnonymousType#2>

Contain has error:
error in linq cannot convert from
'System.Collections.Generic.IEnumerable' to
'System.Linq.IQueryable
Why?
public class ListQuestionAssessor
{
public string sum { get; set; }
public int QuestionsID { get; set; }
public int AssessorID { get; set; }
public int AssessID { get; set; }
}
List<ListQuestionAssessor> objList = new List<ListQuestionAssessor>();
var lisaAddAssessor = (from p in objList
where
!
(from assessor in ObjNotIn
select new
{
assessor.id
}).Contains(new { p.QuestionsID })
select new QuestionAssessor()
{
QuestionsID = initQuestionerId
}).ToList();
I would say that the problem is that you are trying to check
.Contains(new { p.QuestionsID })
Contains is expecting a parameter of the same type as the collection. and since the collection is anonymous, this is probably what caused the error.
rewrite it as
.Any(a => a.id == p.QuestionsID)

Linq assign to a List<> List Binding

I'm getting this error at running time:
Exception Details: System.InvalidOperationException: Unhandled binding type: ListBinding
Choice_A, _b,_C are strings
'Choices' is List
var qs = (from questions in dc.Survey_Questions
where questions.Survey_ID == surveyid
select new SQuestions
{
QuestionID = questions.Question_ID,
Description = questions.Description,
Choice_A = questions.Choice_A,
Choice_B = questions.Choice_B,
Choice_C = questions.Choice_C,
**Choices = {questions.Choice_A, questions.Choice_B,
questions.Choice_C}**
}).ToList();
Basically I'd like to know how I assign to List Choices values of Choice_A,Choice_B, Choice_C.
Thanks in advance.
You can try this. If you already have the following two classes
public class Question
{
public int Question_ID { get; set; }
public int Survey_ID { get; set; }
public String Description { get; set; }
public String Choice_A { get; set; }
public String Choice_B { get; set; }
public String Choice_C { get; set; }
}
public class SQuestions
{
public int QuestionID { get; set; }
public String Description { get; set; }
public String Choice_A { get; set; }
public String Choice_B { get; set; }
public String Choice_C { get; set; }
public List<String> Choices { get; set; }
}
Then the LINQ Query will be
var qs = (from question in dc.Survey_Questions
where question.Survey_ID == surveyid
select new SQuestions
{
QuestionID = question.Question_ID,
Description = question.Description,
Choice_A = question.Choice_A,
Choice_B = question.Choice_B,
Choice_C = question.Choice_C,
Choices = new List<string>(new String[] { question.Choice_A,question.Choice_B, question.Choice_C })
}).ToList();
LINQ to Entities does not support this binding syntax. You should be able to do this, though:
qs = (from questions in dc.Survey_Questions
where questions.Survey_ID == surveyid
select new SQuestions
{
QuestionID = questions.Question_ID,
Description = questions.Description,
Choice_A = questions.Choice_A,
Choice_B = questions.Choice_B,
Choice_C = questions.Choice_C,
Choices = new List<string> {questions.Choice_A, questions.Choice_B,
questions.Choice_C}
}).ToList();

LINQ - Combine multiple lists to form a new list and align them by key?

I have two list of different columns, but each list have a common column with the same key, how do I combine them into a new list, i.e:
public class TradeBalanceBreak
{
public int CommID { get; set; }
public int CPFirmID { get; set; }
public double CreditDfferenceNotional { get; set; }
public string Currency { get; set; }
}
public class Commission
{
public int CommID { get; set; }
public PeriodStart { get; set; }
public ResearchCredit { get; set; }
}
public class CommissionList
{
public List<Commission> Commissions { get { return GetCommissions(); }}
private List<Commission> GetCommissions()
{
// retrieve data code ... ...
}
}
public class TradeBalanceBreakModel
{
public List<TradeBalanceBreak> TradeBalanceBreaks { get; set; }
}
public class CommissionModel
{
public List<CommissionList> CommissionLists { get; set; }
}
What I would like to achieve is to combine/flatten the TradeBalancesBreaks and CommissionLists (from the model classes) into one. The CommID is shared between the two.
Thanks.
Using Join (extension method version) -- after your update
var list1 = GetTradeBalanceBreaks();
var list2 = new CommisionsList().Commissions;
var combined = list1.Join( list2, l1 => l1.ID, l2 => l2.First().ID,
(l1,l2) = > new
{
l1.CommID,
l1.CPFirmID,
l1.CreditDifferenceNotional,
l1.Currency,
PeriodStarts= l2.SelectMany( l => l.PeriodStart ),
ResearchCredits = l2.SelectMany( l => l.ResearchCredit )
})
.ToList();
var combined = from p in PhoneNumbers
join a in Addresses on a.ID equals p.ID
select new {
ID = p.ID,
Name = p.Name,
Phone = p.Phone,
Address = a.Address,
Fax = a.Fax
};

Resources