Refactoring C# code - doing more within Linq - c#

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.

Related

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

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

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)

How do you reuse mapping functions on Nested entities in Entity Framework?

I have seen multiple questions that are similar to this one but I think my case is slightly different. I'm using EF6 to query the database and I'm using data projection for better queries.
Given that performance is very important on this project I have to make sure to just read the actual fields that I will use so I have very similar queries that are different for just a few fields as I have done this I have noticed repetition of the code so I'm been thinking on how to reuse code this is currently what I Have:
public static IEnumerable<FundWithReturns> GetSimpleFunds(this DbSet<Fund> funds, IEnumerable<int> fundsId)
{
IQueryable<Fund> query = GetFundsQuery(funds, fundsId);
var results = query
.Select(f => new FundWithReturns
{
Category = f.Category,
ExpenseRatio = f.ExpenseRatio,
FundId = f.FundId,
Name = f.Name,
LatestPrice = f.LatestPrice,
DailyReturns = f.FundDailyReturns
.Where(dr => dr.AdjustedValue != null)
.OrderByDescending(dr => dr.CloseDate)
.Select(dr => new DailyReturnPrice
{
CloseDate = dr.CloseDate,
Value = dr.AdjustedValue.Value,
}),
Returns = f.Returns.Select(r => new ReturnValues
{
Daily = r.AdjDaily,
FiveYear = r.AdjFiveYear,
MTD = r.AdjMTD,
OneYear = r.AdjOneYear,
QTD = r.AdjQTD,
SixMonth = r.AdjSixMonth,
ThreeYear = r.AdjThreeYear,
YTD = r.AdjYTD
}).FirstOrDefault()
})
.ToList();
foreach (var result in results)
{
result.DailyReturns = result.DailyReturns.ConvertClosingPricesToDailyReturns();
}
return results;
}
public static IEnumerable<FundListVm> GetFundListVm(this DbSet<Fund> funds, string type)
{
return funds
.Where(f => f.StatusCode == MetisDataObjectStatusCodes.ACTIVE
&& f.Type == type)
.Select(f => new FundListVm
{
Category = f.Category,
Name = f.Name,
Symbol = f.Symbol,
Yield = f.Yield,
ExpenseRatio = f.ExpenseRatio,
LatestDate = f.LatestDate,
Returns = f.Returns.Select(r => new ReturnValues
{
Daily = r.AdjDaily,
FiveYear = r.AdjFiveYear,
MTD = r.AdjMTD,
OneYear = r.AdjOneYear,
QTD = r.AdjQTD,
SixMonth = r.AdjSixMonth,
ThreeYear = r.AdjThreeYear,
YTD = r.AdjYTD
}).FirstOrDefault()
}).OrderBy(f=>f.Symbol).Take(30).ToList();
}
I'm trying to reuse the part where I map the f.Returns so I tried created a Func<> like the following:
private static Func<Return, ReturnValues> MapToReturnValues = r => new ReturnValues
{
Daily = r.AdjDaily,
FiveYear = r.AdjFiveYear,
MTD = r.AdjMTD,
OneYear = r.AdjOneYear,
QTD = r.AdjQTD,
SixMonth = r.AdjSixMonth,
ThreeYear = r.AdjThreeYear,
YTD = r.AdjYTD
};
and then use like this:
public static IEnumerable<FundListVm> GetFundListVm(this DbSet<Fund> funds, string type)
{
return funds
.Where(f => f.StatusCode == MetisDataObjectStatusCodes.ACTIVE
&& f.Type == type)
.Select(f => new FundListVm
{
Category = f.Category,
Name = f.Name,
Symbol = f.Symbol,
Yield = f.Yield,
ExpenseRatio = f.ExpenseRatio,
LatestDate = f.LatestDate,
Returns = f.Returns.Select(MapToReturnValues).FirstOrDefault()
}).OrderBy(f=>f.Symbol).Take(30).ToList();
}
The compiler is ok with it but at runtime, it crashes and says: Internal .NET Framework Data Provider error 1025
I tried to convert the Func into Expression like I read on some questions and then using compile() but It didn't work using AsEnumerable is also not an option because It will query all the fields first which is what I want to avoid.
Am I trying something not possible?
Thank you for your time.
It definitely needs to be Expression<Func<...>>. But instead of using Compile() method (not supported), you can resolve the compile time error using the AsQueryable() method which is perfectly supported (in EF6, the trick doesn't work in current EF Core).
Given the modified definition
private static Expression<Func<Return, ReturnValues>> MapToReturnValues =
r => new ReturnValues { ... };
the sample usage would be
Returns = f.Returns.AsQueryable().Select(MapToReturnValues).FirstOrDefault()

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,

EF4 insert entity with childs. Using POCO T4 template

I'm literally going crazy with this. I receive from the HTML the data of one "Father" entity, along with the data of three child entities.
In my modelbinder, I create stub entities for relationships, containing only the primary key.
This is my ModelBinder code:
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
DefaultModelBinder binder = new DefaultModelBinder();
var estimate = (Estimate)binder.BindModel(controllerContext, bindingContext);
estimate.Id = Guid.NewGuid();
estimate.OwnerSociety = ModelBinderHelper.MapComplexType<OwnerSociety, int>(controllerContext, "OwnerSociety", int.Parse, c => c.IDOwnerSociety);
estimate.EstimateType = ModelBinderHelper.MapComplexType<EstimateType, Guid>(controllerContext, "EstimateType", Guid.Parse, c => c.Id);
estimate.Brand = ModelBinderHelper.MapComplexType<Brand, int>(controllerContext, "Brand", int.Parse, c => c.IDBrand);
estimate.FromAccount = ModelBinderHelper.MapComplexType<User, int>(controllerContext, "FromAccount", int.Parse, c => c.IDUser);
estimate.ManagerDirector = ModelBinderHelper.MapComplexType<User, int>(controllerContext, "ManagerDirector", int.Parse, c => c.IDUser);
estimate.Projects.Add(new Project
{
StrategicPlanner = ModelBinderHelper.MapComplexType<User, int>(controllerContext, "StrategicPlanner_1", int.Parse, c => c.IDUser),
Activity = ModelBinderHelper.MapComplexType<Activity, int>(controllerContext, "Activity_1", int.Parse, c => c.IDActivity),
ProjectState = ModelBinderHelper.MapComplexType<ProjectState, int>(controllerContext, "ProjectState_1", int.Parse, c => c.IDProjectState),
StartDate = DateTime.Now,
Name = "XXX1",
Brand = estimate.Brand,
ProjectTypes = new ProjectTypes { IDProjectType = 1 },
Consuntivo = 0,
Order = 1
});
estimate.Projects.Add(new Project
{
StrategicPlanner = ModelBinderHelper.MapComplexType<User, int>(controllerContext, "StrategicPlanner_2", int.Parse, c => c.IDUser),
Activity = ModelBinderHelper.MapComplexType<Activity, int>(controllerContext, "Activity_2", int.Parse, c => c.IDActivity),
ProjectState = ModelBinderHelper.MapComplexType<ProjectState, int>(controllerContext, "ProjectState_2", int.Parse, c => c.IDProjectState),
StartDate = DateTime.Now,
Name = "XXX2",
Brand = estimate.Brand,
ProjectTypes = new ProjectTypes { IDProjectType = 1 },
Consuntivo = 0,
Order = 2
});
estimate.Projects.Add(new Project
{
StrategicPlanner = ModelBinderHelper.MapComplexType<User, int>(controllerContext, "StrategicPlanner_3", int.Parse, c => c.IDUser),
Activity = ModelBinderHelper.MapComplexType<Activity, int>(controllerContext, "Activity_3", int.Parse, c => c.IDActivity),
ProjectState = ModelBinderHelper.MapComplexType<ProjectState, int>(controllerContext, "ProjectState_3", int.Parse, c => c.IDProjectState),
StartDate = DateTime.Now,
Name = "XXX3",
Brand = estimate.Brand,
ProjectTypes = new ProjectTypes { IDProjectType = 1 },
Consuntivo = 0,
Order = 3
});
return estimate;
}
When I try to simply attach my father entity, declare it and the child properties as Added, I get the "there is already an object in the objectstatemanager with the same key". If I try to insert the father entity without childs, it works.
I tried another "way". The following code:
public Estimate CreateEstimate(Estimate toCreate)
{
var brand = Brands.First(c => c.IDBrand == toCreate.Brand.IDBrand);
var estimateType = EstimateTypes.First(c => c.Id == toCreate.EstimateType.Id);
var account = Users.First(c => c.IDUser == toCreate.FromAccount.IDUser);
var manager = Users.First(c => c.IDUser == toCreate.ManagerDirector.IDUser);
var owner = OwnerSocieties.First(c => c.IDOwnerSociety == toCreate.OwnerSociety.IDOwnerSociety);
toCreate.Brand = brand;
toCreate.EstimateType = estimateType;
toCreate.FromAccount = account;
toCreate.ManagerDirector = manager;
toCreate.OwnerSociety = owner;
foreach (var project in toCreate.Projects)
{
project.Activity = Activities.First(c => c.IDActivity == project.Activity.IDActivity);
project.ProjectState = ProjectStates.First(c => c.IDProjectState == project.ProjectState.IDProjectState);
project.StrategicPlanner = Users.First(c => c.IDUser == project.StrategicPlanner.IDUser);
project.ProjectTypes = _entities.ProjectTypes.First();
}
_entities.EstimateSet.AddObject(toCreate);
return toCreate;
}
But it doesn't work, telling me that "INSERT on table "Activities" fails because column "Name" can't be null". But it really shouldn't be inserting anything in the Activity table, in fact I'm retrieving the Activity items from the DB and using them.
Funny, the following code works:
public Estimate CreateEstimate(Estimate toCreate)
{
var estimate = new Estimate();
var brand = Brands.First(c => c.IDBrand == toCreate.Brand.IDBrand);
var estimateType = EstimateTypes.First(c => c.Id == toCreate.EstimateType.Id);
var account = Users.First(c => c.IDUser == toCreate.FromAccount.IDUser);
var manager = Users.First(c => c.IDUser == toCreate.ManagerDirector.IDUser);
var owner = OwnerSocieties.First(c => c.IDOwnerSociety == toCreate.OwnerSociety.IDOwnerSociety);
estimate.Id = toCreate.Id;
estimate.Brand = brand;
estimate.EstimateType = estimateType;
estimate.FromAccount = account;
estimate.ManagerDirector = manager;
estimate.OwnerSociety = owner;
estimate.Date = toCreate.Date;
estimate.Subject = toCreate.Subject;
estimate.Status = toCreate.Status;
estimate.Language = toCreate.Language;
foreach (var project in toCreate.Projects)
{
var project1 = new Project();
project1.Activity = Activities.First(c => c.IDActivity == project.Activity.IDActivity);
project1.ProjectState = ProjectStates.First(c => c.IDProjectState == project.ProjectState.IDProjectState);
project1.StrategicPlanner = Users.First(c => c.IDUser == project.StrategicPlanner.IDUser);
project1.StartDate = project.StartDate;
project1.Name = project.Name;
project1.Brand = brand;
project1.ProjectTypes = _entities.ProjectTypes.First();
estimate.Projects.Add(project1);
}
_entities.EstimateSet.AddObject(estimate);
return toCreate;
}
But this means recreating the father entity, copying over the values, then recreating every child entity, copying the values, assigning it to the father entity, etc. That's a pain, and I don't want to write verbose code like this.
ORM should ease me of the pain of writing tons of code for CRUD operations but it seems like this is not the case.
Anyone can help me?
I had the exact same problem, and had the exact same pains when trying to use EF in a disconnected scenario. And I solved it the same way as you did: reload the object, and 'replay' the changes on the domain object (you have to apply a trick to make optimistic concurrency work though). This was the only way I found to make it work.
Another way to make this work is to send the original entity together with the modified entity. Then you don't have to reload it, but just attach the original entity and replay the changes. But I'm not sure about the overhead if you send all data twice, don't really like that.
Any other way, just fails, crashes, because it seems to mix up contexts, even if they are disposed already.

Categories

Resources