I'm trying to convert a complex domain model into a CSV format. However the dynamic structure of the model is pushing my LINQ wizardry to its limit :)
The desired result should look something like the following. A header row, then data rows.
Note that some fields are empty and others have multiple.
Output
PropA;RolenameA;RolenameB;RolenameC
123;username1,username2;username1;username2
321;;;username1
I wish to create a LINQ query which dynamically can add properties for each Role and then fill that property with the correct username(s).
I recognize that the header and the data must be in 2 different statements.
Pseudo code
var result = new { "PropA", Roles.ForEach(x => x.Rolename) }
result += query.Select(x => new { x.PropA, x.UserRoles.ForEach(...) });
Is what I'm trying to do not possible with LINQ and I should do it "manually" with a dynamic object, or is there some black magic I need to learn :)?
Model
public class A
{
public int PropA { get; set; }
public IEnumerable<UserRole> UserRoles { get; set; }
}
public class UserRole
{
public Role Role { get; set; }
public User User { get; set; }
}
public class Role
{
public string Rolename { get; set; }
}
public class User
{
public string Username { get; set; }
}
I ended up doing it "manually" with an ExpandoObject.
foreach (var item in items)
{
var obj = new ExpandoObject() as IDictionary<string, Object>;
obj.Add("PropA", item.PropA);
foreach (var role in roles)
{
obj.Add(role.Name,
String.Join(",", item.UserRole.Where(x => x.Role == role).Select(x => x.User.Name)));
}
// omitted...
}
Related
I have three classes:
public class M2ArticleMain
{
public int Id { get; set; }
public List<M2ArticleAttributeWeb> Attribut_Web { get; set; }
}
public class M2ArticleAttributeWeb
{
public int Web_Id { get; set; }
public M2ArticleTmpMainSkus Variants { get; set; }
}
public class M2ArticleTmpMainSkus
{
public DateTime TimeAdded { get; set; }
public List<string> Skus { get; set; }
}
And I have two Lists in my code like this:
List<M2ArticleMain> data = new List<M2ArticleMain>();
List<M2ArticleAttributeWeb> attb = new List<M2ArticleAttributeWeb>();
In some part of my code firstly I (from foreach loop) add data to attb list where I add only only some data (because I don't have all data at this point), like this:
...
attb.Add(new M2ArticleAttributeWeb
{
Web_id = item.Id, //(item is from foreach loop)
Variants = null //this is **importat**, I left null for later to add it
});
Next, after I fill attb, I add all this to data list:
...
data.Add(new M2ArticleMain
{
Id = item.Id_Pk, //this is also from foreach loop,
Attribut_Web = attb //now in this part I have only data for Web_id and not Variants
}
Now my question is How to Add items later to data list to object Variants?
Something like this:
data.AddRange( "how to point to Variants" = some data);
The M2ArticleAttributeWeb type holding your Variants property is the member of a collection. That is, there are potentially many of them. You can reference an individual Variants property like this:
data[0].Attribut_Web[0].Variants
But you need to know which items you want to add map to which data and Attribut_Web indexes/objects in order to assign them properly. That probably means another loop, or even a nested loop. That is, you can see all of your Variants properties in a loop like this:
foreach(var main in data)
{
foreach(var attrw in main)
{
var v = attrw.Variants;
// do something with v
Console.WriteLine(v);
// **OR**
attrw.Variants = // assign some object
}
}
It's also much better practice to create your collection properties with the object, and then give them private set attributes:
public class M2ArticleMain
{
public int Id { get; set; }
public List<M2ArticleAttributeWeb> Attribut_Web { get; private set; } = new List<M2ArticleAttributeWeb>();
}
public class M2ArticleAttributeWeb
{
public int Web_Id { get; set; }
public M2ArticleTmpMainSkus Variants { get; set; }
}
public class M2ArticleTmpMainSkus
{
public DateTime TimeAdded { get; set; }
public List<string> Skus { get; private set; } = new List<string>();
}
Now instead of assigning Attribut_Web = attb, you would need to .Add() to the existing List.
TLDR - The error is:
The query has been configured to use 'QuerySplittingBehavior.SplitQuery' and contains a collection in the 'Select' call, which could not be split into separate query. Please remove 'AsSplitQuery' if applied or add 'AsSingleQuery' to the query.
I am developing a backend with EntityFrameworkCore in C#.
My table classes are like this:
public class MainTable : BasicAggregateRoot<int>
{
public MainTable()
{
this.Operations = new HashSet<OperationTable>();
}
public long? RecId { get; set; }
public int FormStatus { get; set; }
public virtual ICollection<OperationTable> Operations { get; set; }
}
public class OperationTable : BasicAggregateRoot<int>
{
public OperationTable()
{
this.Works = new HashSet<Work>(); //Not important things
this.Materials = new HashSet<Material>(); //Not important things
}
public string ServiceType { get; set; }
}
And my DTOs are like this:
public class MainDto : EntityDto<int>
{
public long? RecId { get; set; }
public int FormStatus { get; set; }
public List<OperationDto> Operations { get; set; }
}
public class OperationDto
{
public string ServiceType { get; set; }
}
I created maps this way:
CreateMap<MainTable, MainDto>().ReverseMap();
CreateMap<OperationTable, OperationDto>().ReverseMap();
When I commit the mapping by:
class Service{
IRepository<MainTable, int> _mainTableRepository;
Service(IRepository<MainTable, int> mainTableRepository){
_mainTableRepository = mainTableRepository;
}
List<MainDto> All()
{
var result = mainTableRepository.Include(p => p.Operations)
.ProjectTo<MainDto>(ObjectMapper.GetMapper().ConfigurationProvider) //Here is the problem.
.ToList();
return result;
}
}
I get the error on the top.
When I get rid of the List from mainDto, error does not occur, but I don't have the result that I want either.
What might be the problem? I couldn't find an answer.
In that source you can find the differences between single and split query: https://learn.microsoft.com/en-us/ef/core/querying/single-split-queries
The problem is (I guess) IRepository.Include uses split query by default. But (again I guess) AutoMapper is not configured to use split query, it works with single queries.
We need to change the query type before mapping like this:
var result = mainTableRepository.Include(p => p.Operations)
.AsSingleQuery() //This solved the problem
.ProjectTo<MainDto>(ObjectMapper.GetMapper().ConfigurationProvider)
.ToList();
I have two related models and want to read via linq
using (var ctx = new TextsContext())
{
var data = from e in ctx.Text
where e.LanguageCode == lang
select e;
foreach (var d in data)
{
Debug.WriteLine(d.Language, d.Fieldname);
}
}
First model
public class Language
{
public string Code { get; set; }
public string Country { get; set; }
}
Second model
public class Text
{
public string Fieldname { get; set; }
public string LanguageCode { get; set; } // Add this foriegn key property
public string Description { get; set; }
// Navigation properties
public virtual Language Language { get; set; }
}
I am using code first(Fluent API) to build relationship between the two tables.
When I want to query with linq, I've got error message:
There is already an open DataReader associated with this Command which
must be closed first.
I assume that if you do a the Include() and ToList() it will solve it. Probably because of the lazy loading of linq while it iterates it creates another reader in order to get the other entity (Language).
using (var ctx = new TextsContext())
{
var data = (from e in ctx.Text.Include("Language")
where e.LanguageCode == lang
select e).ToList();
foreach (var d in data)
{
Debug.WriteLine(d.Language, d.Fieldname);
}
}
Goal: to save ViewModel object by Entity Framework. I have UserViewModel object which has list of UnitViewModel. Then, I have a UserAdapter class which converts UserViewModel into Entity Framework User object (see Convert()below how).
Now, my question is how do I convert this list of UnitViewModel to its corresponding Entity Framework Unit list? - Do I have to get each object from DB Context by calling something like context.Units.Where(u=>myListofUnitIDs.Contains(u.UnitID))?
public class UserViewModel
{
public Guid? UserID { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
public DateTime? CreateTime { get; set; }
public List<UnitViewModel> UserUnits { get; set; }
}
public class UnitViewModel
{
public Guid UnitID { get; set; }
public string Name { get; set; }
public int? SortIndex { get; set; }
public DateTime CreateTime { get; set; }
public bool Assigned { get; set; }
}
public class UserAdapter
{
public static User Convert(UserViewModel userView)
{
User user;
if (userView.UserID.HasValue)
{
using (var provider = new CoinsDB.UsersProvider())
{
user = provider.GetUser(userView.UserID.Value);
}
}
else
{
user = new User();
}
user.FirstName = userView.FirstName;
user.LastName = user.LastName;
user.Password = StringHelper.GetSHA1(userView.Password);
user.UserName = user.UserName;
user.CreateTime = DateTime.Now;
// Problem here :)
// user.Units = userView.UserUnits;
return user;
}
}
UPDATE: The main concern here is that I have to retrieve each Unit from database to match (or map) it with ViewModel.Unit objects, right? Can I avoid it?
For your information, this operation is called as Mapping mainly. So, you want to map your view model object to the entity object.
For this, you can either use already existed 3rd party library as AutoMapper. It will map properties by reflection which have same name. Also you can add your custom logic with After method. But, this approach has some advantages and disadvantages. Being aware of these disadvantages could help you to decide whether you must use this API or not. So, I suggest you to read some articles about advantages and disadvantages of AutoMapper especially for converting entities to other models. One of such disadvantages is that it can be problem to change the name of one property in the view model in the future, and AutoMapper will not handle this anymore and you won't get any warning about this.
foreach(var item in userView.UserUnits)
{
// get the mapped instance of UnitViewModel as Unit
var userUnit = Mapper.Map<UnitViewModel, UserUnit>(item);
user.Units.Add(userUnit);
}
So, I recommend to write your custom mappers.
For example, I have created a custom library for this and it maps objects lik this:
user.Units = userView.UserUnits
.Select(userUnitViewModel => userUnitViewModel.MapTo<UserUnit>())
.ToList();
And I am implementing these mapping functions as:
public class UserUnitMapper:
IMapToNew<UnitViewModel, UserUnit>
{
public UnitViewModel Map(UserUnit source)
{
return new UnitViewModel
{
Name = source.Name,
...
};
}
}
And then in runtime, I am detecting the types of the objects which will be used during mapping, and then call the Map method. In this way, your mappers will be seperated from your action methods. But, if you want it urgently, of course you can use this:
foreach(var item in userView.UserUnits)
{
// get the mapped instance of UnitViewModel as Unit
var userUnit= new UserUnit()
{
Name = item.Name,
...
};
user.Units.Add(userUnit);
}
I am trying to store a Json serialized EF object in another database.
One of the items I'm strying to store is the cart, which has some relateded tables.
How can I store the cart, without dragging its relations along (preferably without resorting to .Select() to handpick the distinct columns)
[AllowAnonymous]
public ActionResult Test() {
using (var db = new DALEntities())
{
var q = db.tblCarts.SingleOrDefault(x => x.CartItemID == 4275);
q.tblContactsExtra = null;
q.TBLINVENTORY = null;
var settings = new JsonSerializerSettings {PreserveReferencesHandling = PreserveReferencesHandling .Objects} ;
var str = JsonConvert.SerializeObject(q, settings);
return Content(str);
}
Unfortunately, you'll have to set your foreign keys to allow nulls. Otherwise you won't be able to achieve what you're looking to achieve. To do that, just make the ID fields nullable, something like:
public int? ContactsExtraId { get; set; }
public virtual tblContactsExtra tblContactsExtra { get; set; }
public int? InventoryId { get; set; }
public virtual TblInventory TBLINVENTORY { get; set; }