Dynamically use DisplayName as alias in Linq query from entity - c#

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.

Related

Return "complex" object in EF Core

I have a class which looks like this:
public class Settings
{
public int Id { get; set; }
public string Property { get; set; }
public string Value { get; set; }
[NotMapped]
public List<SettingsProperty> Properties { get; set; }
}
With my database table looking like this:
Id Property Value
13 prop1 valueProp1
13 prop2 valueProp2
2 prop1 valueProp1
What I want to do is, given an Id, return an object of type Settings and store the properties and values inside the list Properties. So if I fetch records with Id 13, I would want something like this (kind of a json syntax to better explain it):
Id = 13
Properties = [{Name = "prop1", Value = "valueProp1"}, {Name = "prop2", Value = "valueProp2"}]
I thought I could maybe group results by Id, but this is not working and I can't think of any other way to do this. This is what I have:
_context.Settings
.Where(d => d.Id == Id)
.Select(x => new Settings()
{
Id = x.Id,
Properties = new List<SettingsProperty>()
{
new SettingsProperty()
{
Name = x.Property,
Value = x.Value
}
}
})
.AsNoTracking()
But this only gets me the one record. Could you help me, please?

Linq Value cannot be null on FK

Using C# MVC5 Visual studio 2015.
I have a method that contains the following code:
public List<OffersOnPropertyViewModel> Build(string buyerId)
{
var filtered = _context.Properties.Where(x => x.Offers.Any(c => c.BuyerUserId == buyerId)).ToList();
var model = filtered.Select(c =>
{
var item = new OffersOnPropertyViewModel()
{
PropertyType = c.PropertyType,
NumberOfBedrooms = c.NumberOfBedrooms,
StreetName = c.StreetName,
Offers = c.Offers.Where(d => d.BuyerUserId == buyerId).Select(x => new OfferViewModel
{
Id = x.Id,
Amount = x.Amount,
CreatedAt = x.CreatedAt,
IsPending = x.Status == OfferStatus.Pending,
Status = x.Status.ToString(),
BuyerUserId = x.BuyerUserId
}),
};
return item;
}).ToList();
//TODO: refactor, shorten linq, duping where clause
return model;
}
Here is the model:
public class Property
{
[Key]
public int Id { get; set; }
[Required]
public string PropertyType { get; set; }
[Required]
public string StreetName { get; set; }
[Required]
public string Description { get; set; }
[Required]
public int NumberOfBedrooms { get; set; }
[Required]
public string SellerUserId { get; set; }
public bool IsListedForSale { get; set; }
public ICollection<Offer> Offers { get; set; }
}
In the DB Offers table has the property id as its FK.
The method fails at runtime saying the Value cannot be null.
When I step through I notice the filtered results (in the example its 1 result), is saying offers is null. Although the query just filtered the results based on "x.Offers".
I simply need a way to retrieve a list of property's that have offers made by the buyerId provided. Is my approach wrong? or am i missing a one liner?
Thanks
You will need to add Include() to your LINQ query to bring in child objects, as follows:
var filtered = _context.Properties.Include("Offers")
.Where(x => x.Offers.Any(c => c.BuyerUserId == buyerId)).ToList();
The reason your filter works with the Any() is because when generating the SQL query, this part forms the WHERE clause and is not included in the SELECT.

Entity Framework add local data to list from database

I am pretty new to Entity Framework and I am using this method in order to query through my database:
var _context = new StudioEntities();
var results = _context.tblStudios.Select(u => new
{
u.Standort,
u.Name,
u.Id
}).ToList();
Now my goal is to add local data as well which isn't present in the database. I tried it with this code but it didn't work:
results.Add(new tblStudio { Id = 0, Name = "Gesamt" });
Can someone help me with this? Thanks
Edit:
My table class looks like this:
public partial class tblStudio
{
public int Id { get; set; }
public string Name { get; set; }
public string Standort { get; set; }
public Nullable<int> Plz { get; set; }
}
The result is not a List of tblStudios, it is a List of Anonymous Type. So if you want to add an item to the result you should do like this:
var results = _context.tblStudios.Select(u => new tblStudiosDTO()
{
Standort = u.Standort,
Name = u.Name,
Id = u.Id
}).ToList();
results.Add(new tblStudiosDTO() { Id = "0", Name = "Gesamt" });
But because you cannot project onto a mapped entity then you need to create a DTO class like tblStudiosDTO with needed properties from the tblStudios entity.
public class tblStudiosDTO
{
public string Standort { get; set; }
public string Name { get; set; }
public string Id { get; set; }
}

There has to be a better way to add these using LINQ, right?

I am new to LINQ and and come up with the below to add new information to my DB using LINQ and EF5 but I am sure there is a more efficant, better, way to do this I just don't know it. I was hoping to get some input on what I can do to acceive the same but with less/more efficant code.
using (var db = new FullContext())
{
if (ddlItemType.SelectedValue == "Other")
{
var NewItemType = new ItemType { Name = tbNewType.Text };
db.ItemTypes.Add(NewItemType);
db.SaveChanges();
}
if (ddlRegion.SelectedValue == "Other")
{
var NewRegion = new ReleaseRegion { Name = tbNewRegion.Text };
db.Regions.Add(NewRegion);
db.SaveChanges();
}
var NewItemTypeID = byte.Parse((from i in db.ItemTypes
where i.Name == tbNewType.Text
select new { i.ID }).ToString());
var NewRegionID = byte.Parse((from r in db.Regions
where r.Name == tbNewRegion.Text
select new { r.ID }).ToString());
var NewItem = new Item
{
Name = tbItemName.Text,
TypeID = NewItemTypeID,
RegionID = NewRegionID,
Condition = ddlCondition.SelectedValue.ToString(),
UPC = tbUPC.Text,
ISBN = tbISBN.Text,
IsColleciton = cbIsCollection.Checked,
CollectionID = Convert.ToInt16(ddlCollection.SelectedValue),
Notes = tbNotes.Text
};
db.Items.Add(NewItem);
db.SaveChanges();
}
Item.cs:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace FFCollection.DAL
{
[Table("Items")]
public class Item
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Int16 ID { get; set; }
[Required]
public string Name { get; set; }
public byte TypeID { get; set; }
[ForeignKey("TypeID")]
public virtual ItemType Type { get; set; }
public byte RegionID { get; set; }
[ForeignKey("RegionID")]
public virtual ReleaseRegion Region { get; set; }
[Required]
public string Condition { get; set; }
public string UPC { get; set; }
public string ISBN { get; set; }
public string Notes { get; set; }
[Required]
public Boolean IsColleciton { get; set; }
public Int16 CollectionID { get; set; }
[ForeignKey("CollectionID")]
public virtual Item InCollectionID { get; set; }
}
}
ItemType.cs:
using System.ComponentModel.DataAnnotations.Schema;
namespace FFCollection.DAL
{
public class ItemType
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public byte ID { get; set; }
public string Name { get; set; }
}
}
The databinding to DDL:
using (var db = new FullContext())
{
ddlItemType.DataSource = (from t in db.ItemTypes
select new { t.ID, t.Name }).ToList();
ddlItemType.DataTextField = "Name";
ddlItemType.DataValueField = "ID";
ddlItemType.DataBind();
ddlItemType.Items.Insert(0, new ListItem("Other", "Other"));
}
Part of the trouble isn't Linq, it's how you're using EF. Based on that example code you're using it as a data layer wrapper rather than an ORM. When constructing an object graph you should deal with the objects where you can, not foreign key IDs. The power of an ORM is that you can deal specifically with object graphs that are mapped to data, so that when you tell the ORM to save an object (and it's associated relatives) the ORM takes out all of the work of inserting/updating new records and wiring up keys. You're doing all that extra work in code, where an ORM like EF should allow you to accomplish what you want with a handful of lines.
For a start, when dealing with combo boxes, bind them to a data structure that includes the lookup value's ID that you can resolve instances of existing ItemTypes or Regions to associate with your new Item. (or in the case of selections of "other".
What I'd be looking at would be to bind the combo boxes to ItemType/Regions with the "Other" being a specific place-holder that the code will substitute with a new object if selected based on entries in the text fields. Then rather than saving the new objects before appending to the "Item", you simply set the references and save the Item which should cascade insert operations for the new lookup objects.
After this code executes EF will automatically put an ID into your NewItemType entity. You don't need to go and find it again, you could just say NewItemType.ID. This will only work after you have already called db.SaveChanges().
if (ddlItemType.SelectedValue == "Other")
{
var NewItemType = new ItemType { Name = tbNewType.Text };
db.ItemTypes.Add(NewItemType);
db.SaveChanges();
}

Can I create nested classes when using Linq-To-Entities?

I'm still learning Entity Framework and Linq-To-Entities, and I was wondering if a statement of this kind is possible:
using (var context = new MyEntities())
{
return (
from a in context.ModelSetA.Include("ModelB")
join c in context.ModelSetC on a.Id equals c.Id
join d in context.ModelSetD on a.Id equals d.Id
select new MyModelA()
{
Id = a.Id,
Name = a.Name,
ModelB = new MyModelB() { Id = a.ModelB.Id, Name = a.ModelB..Name },
ModelC = new MyModelC() { Id = c.Id, Name = c.Name },
ModelD = new MyModelD() { Id = d.Id, Name = d.Name }
}).FirstOrDefault();
}
I have to work with a pre-existing database structure, which is not very optimized, so I am unable to generate EF models without a lot of extra work. I thought it would be easy to simply create my own Models and map the data to them, but I keep getting the following error:
Unable to create a constant value of type 'MyNamespace.MyModelB'. Only
primitive types ('such as Int32, String, and Guid') are supported in
this context.
If I remove the mapping for ModelB, ModelC, and ModelD it runs correctly. Am I unable to create new nested classes with Linq-To-Entities? Or am I just writing this the wrong way?
What you have will work fine with POCOs (e.g., view models). Here's an example. You just can't construct entities this way.
Also, join is generally inappropriate for a L2E query. Use the entity navigation properties instead.
I have created your model (how I understand it) with EF 4.1 in a console application:
If you want to test it, add reference to EntityFramework.dll and paste the following into Program.cs (EF 4.1 creates DB automatically if you have SQL Server Express installed):
using System.Linq;
using System.Data.Entity;
namespace EFNestedProjection
{
// Entities
public class ModelA
{
public int Id { get; set; }
public string Name { get; set; }
public ModelB ModelB { get; set; }
}
public class ModelB
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ModelC
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ModelD
{
public int Id { get; set; }
public string Name { get; set; }
}
// Context
public class MyContext : DbContext
{
public DbSet<ModelA> ModelSetA { get; set; }
public DbSet<ModelB> ModelSetB { get; set; }
public DbSet<ModelC> ModelSetC { get; set; }
public DbSet<ModelD> ModelSetD { get; set; }
}
// ViewModels for projections, not entities
public class MyModelA
{
public int Id { get; set; }
public string Name { get; set; }
public MyModelB ModelB { get; set; }
public MyModelC ModelC { get; set; }
public MyModelD ModelD { get; set; }
}
public class MyModelB
{
public int Id { get; set; }
public string Name { get; set; }
}
public class MyModelC
{
public int Id { get; set; }
public string Name { get; set; }
}
public class MyModelD
{
public int Id { get; set; }
public string Name { get; set; }
}
class Program
{
static void Main(string[] args)
{
// Create some entities in DB
using (var ctx = new MyContext())
{
var modelA = new ModelA { Name = "ModelA" };
var modelB = new ModelB { Name = "ModelB" };
var modelC = new ModelC { Name = "ModelC" };
var modelD = new ModelD { Name = "ModelD" };
modelA.ModelB = modelB;
ctx.ModelSetA.Add(modelA);
ctx.ModelSetB.Add(modelB);
ctx.ModelSetC.Add(modelC);
ctx.ModelSetD.Add(modelD);
ctx.SaveChanges();
}
// Run query
using (var ctx = new MyContext())
{
var result = (
from a in ctx.ModelSetA.Include("ModelB")
join c in ctx.ModelSetC on a.Id equals c.Id
join d in ctx.ModelSetD on a.Id equals d.Id
select new MyModelA()
{
Id = a.Id,
Name = a.Name,
ModelB = new MyModelB() {
Id = a.ModelB.Id, Name = a.ModelB.Name },
ModelC = new MyModelC() {
Id = c.Id, Name = c.Name },
ModelD = new MyModelD() {
Id = d.Id, Name = d.Name }
}).FirstOrDefault();
// No exception here
}
}
}
}
This works without problems. (I have also recreated the model from the database (which EF 4.1 had created) in EF 4.0: It works as well. Not surprising since EF 4.1 doesn't change anything in LINQ to Entities.)
Now the question is why you get an exception? My guess is that there is some important difference in your Models or ViewModels or your query compared to the simple model above which is not visible in your code example in the question.
But the general result is: Projections into nested (non-entity) classes work. (I'm using it in many situations, even with nested collections.) Answer to your question title is: Yes.
What Craig posted does not seem to work for nested entities. Craig, if I am misunderstood what you posted, please correct me.
Here is the workaround I came up with that does work:
using (var context = new MyEntities())
{
var x = (
from a in context.ModelSetA.Include("ModelB")
join c in context.ModelSetC on a.Id equals c.Id
join d in context.ModelSetD on a.Id equals d.Id
select new { a, b, c }).FirstOrDefault();
if (x == null)
return null;
return new MyModelA()
{
Id = x.a.Id,
Name = x.a.Name,
ModelB = new MyModelB() { Id = x.a.ModelB.Id, Name = x.a.ModelB..Name },
ModelC = new MyModelC() { Id = x.c.Id, Name = x.c.Name },
ModelD = new MyModelD() { Id = x.d.Id, Name = x.d.Name }
};
}
Since Entity Framework can't handle creating nested classes from within the query, I simply returned an anonymous object from my query containing the data I wanted, then mapped it to the Model

Categories

Resources