Entity Framework Core - Get multiple rows from one table as one DTO with multiple properties - c#

Is it possible to select two rows into one anonymous object DTO with two properties?
With a model like:
public class Document
{
public int Id { get; set; }
public string Text { get; set; }
// Other properties
}
I am writing a method that finds the difference between two versions of a document:
public Task<string> CompareVersions(int initialId, int finalId)
So I need to retrieve the text of exactly two Documents by Id, and I need know which was which.
Currently I am constructing a Dictionary<int, string> by doing:
var dto = await _context.Documents
.Where(doc => doc.Id == initialId
|| doc.Id == finalId)
.ToDictionaryAsync(x => x.Id, x => x.Text);
and then calling dto[initialId] to get the text. However, this feels very cumbersome. Is there any way to take the two Ids and select them into one DTO in the form
{
InitialText,
FinalText
}

You have to use SelectMany
var query =
from initial in _context.Documents
where initial.Id = initialId
from final in _context.Documents
where final.Id = finalId
select new
{
InitialText = initial.Text,
FinalText = final.Text
};
var result = await query.FirstOrDefaultAsync();

Aggregate can do it too
var dto = (await _context.Documents
.Where(doc => doc.Id == initialId || doc.Id == finalId).ToListAsync())
.Aggregate(
new { InitialText = "", FinalText = "" },
(seed, doc) => {
if(doc.Id == initialId)
seed.InitialText = doc.Text;
else
seed.FinalText = doc.Text;
}
);
I'm not sure I like it any more than I do your dictionary approach, but with an actual dto at the end rather than the dictionary:
var d = await _context.Documents
.Where(doc => doc.Id == initialId || doc.Id == finalId)
.ToDictionaryAsync(x => x.Id, x => x.Text);
var dto = new { InitialText = d[initialId], FinalText = d[finalId] };
You could also perhaps just:
var dto = new {
InitialText = await context.Documents
.FindAsync(initialId),
FinalText = await context.Documents
.FindAsync(finalId)
};

Related

Problem returning entity grouped with LINQ in HTTP GET

What I'm doing wrong in this method below? I created a group with linq because I need to group the list by 2 columns and for this grouping I will have a list of files.
[HttpGet]
[Route("versions-by-period")]
public IActionResult GetVersionsByPeriodId(int entityId, int periodId)
{
var versionsInvoiceBillet = db.RemittanceInvoiceBilletVersionsCompacts
.Where(x => x.LegalEntityId == entityId && x.PeriodId == periodId && x.IsCurrent && x.DownloadHash != null)
.GroupBy(x => new { x.LifePolicyNumber, x.LegalEntityGroupNumber },
i => new { i.DownloadHash, i.FileTypeEnum, i.DueDate }, (key, group) => new
{
LifePolicyNumber = key.LifePolicyNumber,
LegalEntityGroupNumber = key.LegalEntityGroupNumber,
Files = group.ToList()
});
return Ok(versionsInvoiceBillet.Select(x => new {
lifePolicyNumber = x.LifePolicyNumber,
legalEntityGroupNumber = x.LegalEntityGroupNumber,
invoiceAndBillet = x.Files.Select(f => new {
downloadHash = f.DownloadHash,
fileTypeEnum = f.FileTypeEnum,
dueDatet = f.DueDate
})
}));
}
If I try to call this method with Postman, the body comes empty. The problem is in invoiceAndBillet information that is returned, if I change to below, the body comes filled.
return Ok(versionsInvoiceBillet.Select(x => new {
lifePolicyNumber = x.LifePolicyNumber,
legalEntityGroupNumber = x.LegalEntityGroupNumber,
invoiceAndBillet = x.Files.Select
}));
If I try to debug the selection that I'm trying to return, I get this message below:

Select multiple times using LINQ based on result

I currently have this dataset
This is the current dataset
So, I am trying to return the data based on the Category Id as an input. But if keyword is not present for the category, then the data from its parent should be returned
This is an example
Input: 202; Output: ParentCategoryID=201, Name=Operating System,
Keywords=Teaching
This is what I have right now (testData is the dataset)
public static string GetData(int categoryId)
{
var searchResult = testData.Where(x => x.CategoryId == categoryId).FirstOrDefault();
//if has no keywords return parent data
if(string.IsNullOrEmpty(searchResult.Keywords))
{
var parentData = testData.Where(x => x.CategoryId == searchResult.ParentCategoryId);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append("ParentCategoryId=");
stringBuilder.Append(parentData.ParentCategoryId);
stringBuilder.Append(", Name=");
stringBuilder.Append(parentData.Name);
stringBuilder.Append(", Keywords=");
stringBuilder.Append(parentData.Keywords);
return stringBuilder.ToString();
}
But this only works with just one parent. It works in this kind of scenarios
Input: 201; Output: ParentCategoryID=200, Name=Computer,
Keywords=Teaching
I think there is an issue with my LINQ query, I would appreciate any advice
A simple while loop might be what you need
int input = 202;
bool flag = true;
var result = datalist.Where(q => q.CatId == input).FirstOrDefault();
var parrent = datalist.Where(q => q.CatId == result.ParrentId).FirstOrDefault();
var keyword = "";
while (String.IsNullOrEmpty(parrent.Keyword))
{
parrent = datalist.Where(q => q.CatId == parrent.ParrentId).FirstOrDefault();
}
keyword = parrent.Keyword;
Get the parent via a recursive function:
public static WhateverYourDataTypeIs GetParent(int parentCategoryId)
var parentData = testData.Where(x => x.CategoryId == parentCategoryId).FirstOrDefault();
if(string.IsNullOrEmpty(parentData.Keywords))
{
return GetParent(parentData.ParentCategoryId);
}
return parentData;
}
And just call that instead of the old way you got the parent
// OLD:
var parentData = testData.Where(x => x.CategoryId == searchResult.ParentCategoryId).FirstOrDefault();
// NEW:
var parentData = GetParent(searchResult.ParentCategoryId)

Search form has an Enum dropdown field whose value may be null (none selected) once it reaches my lambda Linq query

Basically, if the user selected no option from the dropdown combo, I want it to be left out from my Linq query that looks something like this:
// this is how I manage the form post data, please
// propose a better way if you know one
Dictionary<string, string> formdata = new Dictionary<string, string>();
foreach(string key in Request.Form.AllKeys)
{
formdata.Add(key, Request.Form[key]);
}
// getting the title
string title = "";
formdata.TryGetValue("postedTitle", out title);
// getting the level
string levelString = "";
formdata.TryGetValue("postedLevel", out levelString );
int level = -1;
if(levelString != "")
{
Int32.TryParse(levelString , out level);
}
var model = new FooIndexVM
{
Foos = _ctx.SomeDbSet.Where(w => w.Title.Contains(title) && w.Level == (Level?)level.Value).Select(x => new FooBarRow
{
FooBarId = x.Id,
....
Since I'm getting either 0 or -1 for the level -- I need a way to gracefully leave the Enum part from the query completely. I will also later add some additional fields similar to this one (may be unselected) so the solution will also work for those, I guess.
You can chain Where commands so this line:
Foos = _ctx.SomeDbSet.Where(w => w.Title.Contains(title) && w.Level == (Level?)level.Value).Select(x => new FooBarRow
{
FooBarId = x.Id,
....
Could be rewritten to be this without changing its behaviour (multiple Wheres effectively become combined with &&s):
Foos = _ctx.SomeDbSet.Where(w => w.Title.Contains(title)).Where(w => w.Level == (Level?)level.Value).Select(x => new FooBarRow
{
FooBarId = x.Id,
....
This then means that you can add some logic around whether to apply the second Where or not like this, for example:
var query = _ctx.SomeDbSet.Where(w => w.Title.Contains(title));
if (level != -1)
{
query = query.Where(w => w.Level == (Level?)level.Value)
}
Foos = query.Select(x => new FooBarRow
{
FooBarId = x.Id,

Merge two Lists of same type with diff values and avoid duplicates

I have two lists of same type with different key value pairs,
List1 has "isPermanent = true" and List2 has false value and also
List1 has an extra key "nextVacationDate".
Im trying to do union of these as below but im afraid I will still get the duplicates because of different values. I need to merge both lists in to one list and order by List1 first (Permanent employees first)..is there a better way to do this using LINQ?
public newList1 List1(string abcd)
{
var result = serviceMethod1(abcd);
var newList1 = new List<emp>();
if (result == null) return null;
newList.AddRange(
result.Select(x => new Model
{
firstName = x.FName,
secondName = x.SName,
address = x.Address,
employeeId = x.EmpId,
isPermanent = true,
nextVacationDate =x.VacDt,
salary = x.Bsalary
}));
return newList1;
}
public newList2 List2(string defg)
{
var result = serviceMethod2(defg);
var newList2 = new List<emp>();
if (result == null) return null;
newList.AddRange(
result.Select(x => new Model
{
firstName = x.FName,
secondName = x.SName,
address = x.Address,
employeeId = x.EmpId,
isPermanent = false,
salary = x.Bsalary
}));
return newList2;
}
private List<emp> EmployyeList(List<emp> newList1, List<emp> newList2)
{
var sortedEmpList1 = newList1.OrderBy(i => i.Fname);
var sortedEmpList2 = newList2.OrderBy(i => i.Fname);
List<MeterModel> combinedList = newList1.Union(newList2) as List<emp>;
return combinedList;
}
You can filter the 2nd list to avoid duplicates:
newList1.Union(newList2.Where(emp2 => !newList1.Any(emp1 => emp1.employeeId == emp2.employeeId)))

Refactoring C# code - doing more within Linq

The code below is what I currently have and works fine. I feel that I could do more of the work I am doing in Linq instead of C# code.
Is there is anyone out there who can accomplish the same result with more Linq code and less C# code.
public List<Model.Question> GetSurveyQuestions(string type, int typeID)
{
using (eMTADataContext db = DataContextFactory.CreateContext())
{
List<Model.Question> questions = new List<Model.Question>();
List<Linq.Survey_Question> survey_questions;
List<Linq.Survey> surveys = db.Surveys
.Where(s => s.Type.Equals(type) && s.Type_ID.Equals(typeID))
.ToList();
if (surveys.Count > 0)
{
survey_questions = db.Survey_Questions
.Where(sq => sq.Survey_ID == surveys[0].ID).ToList();
foreach (Linq.Survey_Question sq in survey_questions)
{
Model.Question q = Mapper.ToBusinessObject(sq.Question);
q.Status = sq.Status;
questions.Add(q);
}
}
else
{
questions = null;
}
return questions;
}
}
Here is my Mapper function from my Entity to Biz Object
internal static Model.Question ToBusinessObject(Linq.Question q)
{
return new Model.Question
{
ID = q.ID,
Name = q.Name,
Text = q.Text,
Choices = ToBusinessObject(q.Question_Choices.ToList())
};
}
I want my mapper funciton to map the Question Status like so.
internal static Model.Question ToBusinessObject(Linq.Question q)
{
return new Model.Question
{
ID = q.ID,
Name = q.Name,
Text = q.Text,
Choices = ToBusinessObject(q.Question_Choices.ToList()),
Status = q.Survey_Questions[?].Status
};
}
? the issue is this function does not know which survey to pull the status from.
Instead of creating the biz object then setting the Status property in a foreach loop like so
foreach (Linq.Survey_Question sq in survey_questions)
{
Model.Question q = Mapper.ToBusinessObject(sq.Question);
q.Status = sq.Status;
questions.Add(q);
}
I would like to somehow filter the EntitySet<Survey_Question> in the q object above in the calling method, such that there would only be one item in the q.Survey_Questions[?] collection.
below is my database schema and business object schema
What I needed to do was setup a join.
public List<Model.Question> GetSurveyQuestions(string type, int typeID)
{
using (eMTADataContext db = DataContextFactory.CreateContext())
{
return db.Survey_Questions
.Where(s => s.Survey.Type.Equals(type) && s.Survey.Type_ID.Equals(typeID))
.Join(db.Questions,
sq => sq.Question_ID,
q => q.ID,
(sq, q) => Mapper.ToBusinessObject(q, sq.Status)).ToList();
}
}
And then overload my Mapper Function
internal static Model.Question ToBusinessObject(Linq.Question q, string status)
{
return new Model.Question
{
ID = q.ID,
Name = q.Name,
Text = q.Text,
Status = status,
Choices = ToBusinessObject(q.Question_Choices.ToList()),
};
}
from question in db.Survey_Questions
let surveys = (from s in db.Surveys
where string.Equals(s.Type, type, StringComparison.InvariantCultureIgnoreCase) &&
s.Type_ID == typeID)
where surveys.Any() &&
surveys.Contains(s => s.ID == question.ID)
select new Mapper.Question
{
ID = question.Id,
Name = question.Name,
Text = question.Text,
Choices = ToBusinessObject(question.Question_Choices.ToList()),
Status = question.Status
}
Does that get you on the right track?
Why are you duplicating all your classes? You could just extend the LINQ to SQL classes with your business logic - they are partial classes. This is somewhat against the purpose of an OR mapper - persisting business entities.

Categories

Resources