EF-CF-Set 'ignored' property from another table value - c#

I am using Entity Framework Code-First approach. Below is my sample Model.
public class LocalizableEntity
{
public int Id{get;set;}
// this property is 'Ignore'd. Need to set this.
public string Name{get;set;}
// this is the collection of 'Name's in all supported cultures.
public virtual ICollection<LocalizationText> LocalNames{get;set;}
}
Using fluent API, the 'Name' property will be ignored, like Ignore(t=>t.Name). My idea is to set the Name property from LocalNames collection by querying with a given culture ID. The LocalizationText type will look like below.
public class LocalizationText
{
public int Id{get;set;}
public string Text{get;set;}
public string Culture{get;set;}
}
I want to implement methods to SELECT the items in SINGLE, ALL and BY PREDICATE in my repository(please consider LocalizableEntities and LocalizationText are the DbSets), but with populating the Name property in one query.
GET SINGLE ITEM method
public LocalizableEntity GetById(int id)
{
var result=LocalizableEntities.Find(id); // selects the item
//the culture will be passed in by other way. Hard-coding here
result.Name=LocalizableEntities.LocalNames.Single(t=>t.Culture=="en-us");
return result;
}
But the above implementation will take two DB calls. I want to make this into one query/expression. I also need to do this when selecting in batch also, assigning value for Name over sequence.
Is this possible with single query? If yes, can anyone please guide me?
Thanks :)

Ignoring the entity model field kind of smells. Your design seems well suited to a ViewModel. So then assuming you have an entity model called LocalizationText and a ViewModel called LocalizableEntityVM you could do something like:
public LocalizableEntityVM GetById(int id, string culture)
{
var result=LocalizationText.Single(le => le.id && le.Culture == culture)
.Select(le => new LocalizableEntityVM
{
Name = le.Text
});
return result;
}

Related

Extend Entity Object to include a calculated property

Let's say I have an Entity Object 'Jewels' that has the properties 'Name' and 'Birthdate'.
I want to implement a LINQ query that returns an object that has 'Name', 'Birthdate' and 'Birthstone'. So I extend 'Jewels' like this:
public partial class JewelStones : Jewels
string Birthstone = null;
public void JewelsWithStone()
{
this.Birthstone = "diamond";
//(we figure out what stone applies to the month here)
}
I can get this far, and I THINK I'm on the right track, but I don't know how to write a LINQ query and get back an object that includes Birthstone, so I can bind that object to a grid that will show Birthstone, which I'm not storing anywhere, as it's always calculated (this is pretend data, sorry if it's not logical).
List<Jewel> jewels = new List<Jewel>;
using (jewelentities db = new jewelentities())
{
jewels = (from j in db.Jewels select j).ToList();
}
How do I fill up my JewelStone object with Name, Birthdate, and Birthstone?
If I'm not following best practice here, please let me know!
EDIT
I've tried adding a partial class to the Entity partial class. When I reference the Jewel class now, it 'sees' the Birthstone property, but it is null. I don't know why? Here is the partial class:
public partial class Jewel
{
private string _birthstone;
public string Birthstone
{
get { return _birthstone; }
set
{
JewelBusiness jewelBusiness = new JewelBusiness();
_birthstone = jewelBusiness.RequestBirthstone(birthmonth);
}
}
}
If I use LINQ to query the entity to get a list of Jewel records, I get all the info from the Entity, Jewel.Birthstone is there, but it is null. However if I do a foreach on the results ---
foreach (Jewel j in jewels)
{
string stone = jewelBusiness.RequestBirthstone(j.Birthmonth);
}
stone will equal the expected result (birthstone for that month).
Why doesn't my partial class return the birthstone??
I'm not sure I understand your requirement correctly. But if you don't want to store Birthstone but calculate it on the fly, just change your code to
public partial class Jewel
{
private string _birthstone;
public string Birthstone
{
get
{
if (_birthstone == null)
{
JewelBusiness jewelBusiness = new JewelBusiness();
_birthstone = jewelBusiness.RequestBirthstone(birthmonth);
}
return _birthstone;
}
}
}
Isn't your Jewels EntityObject in a partial class too? You can most likely just add a Jewels partial class to "extend" it and add the wanted property there.
For me, it depends on where the logic for the calculated column resides.
If it resides in database, then you must do join query in the Linq. I assume in this case, you has a table named BirthStoneTable, with the month as the relation. I don't suggest to add a ternary operation inside linq query, such as select j.BirthDate.Month == 1 ? "Diamond" : //etc etc. It is hard to debug and to track (moreover for code coverage reason).
If it resides in UI specific (only to improve the display), I usually add a type-casted class, such as:
public class JewelUI{
public explicit operator JewelUI(Jewel jewel){
JewelUI jewelUI = new JewelUI();
// assign birthdate and name
jewelUI.BirthStone = GetBirthStone(jewel.BirthDate.Month);
}
public string BirthStone{get;set;};
public string GetBirthStone(int month){
if(month == 1) return "Diamond";
//etc etc
}
}
If the calculated column is used in the business logic, usually I handle the calculation in service / business logic. All of it to ensure the good Separation of Concern.
NB: I may misunderstand your requirement though

cast Table<T> to something

I have a datacontext, and it has Authors table.
public partial Author:IProductTag{}
I want to cast Table<Authors> object to Table<IProductTag>, but that appears to be impossible. I am trying to do that because I want my method to be able to work with different tables which come as input parameters. To be more specific, I need to execute OrderBy and Select methods of the table. I have few other tables, entities of which implement IProductTag . Also, I tried to write a function like:
public static void MyF<t>(){
Table<t> t0 = (Table<t>)DataContext.GetMyTableUsingReflection();
}
But it fails at compile-time. And if I cast the table to something like ITable or IQueriable, then the OrderBy and Select functions simply don't work. So how do you deal with it?
I suspect you want to make your method generic - so instead of
void DoSomethingWithTable(Table<IProductTag> table)
you should have
void DoSomethingWithTable<T>(Table<T> table) where T : class, IProductTag
That should work fine, assuming you only need to read entities (and apply query operators etc). If that doesn't work for you, please give more details about what your method needs to do.
(You say that your attempt to use reflection failed, but you haven't said in what way it failed. Could you give more details?)
I have no idea what a ProductTag is so I've used different types to show my solution to this problem. Yes there doesn't seem to be a way to get a Table<T>, but you can get IQueryable<T> which works just as well (at least for my situation).
I have a simple analytics database, where each website has its own table containing both generic and specific items. I wanted to use an interface for the shared data.
public interface ISession
{
public DateTime CreateDt {get; set; }
public string HostAddress {get; set; }
public int SessionDuration {get; set; }
}
public static IQueryable<ISession> GetQueryableTable(MyDataContext db, string site)
{
Type itemType;
switch (item)
{
case "stackoverflow.com":
itemType = typeof(Analytics_StackOverflow);
break;
case "serverfault.com":
itemType = typeof(Analytics_ServerFault);
break;
default: throw Exception();
}
return db.GetTable(itemType).Cast<ISession>();
}
You can then do a query like this :
var table = GetQueryableTable(db, "stackoverflow.com");
var mySessions = table.Where(s => s.HostAddress == MY_IP);
To create a new row you can use reflection :
var rowType = typeof(Analytics_ServerFault);
var newRow = (ISession) rowType.GetConstructor(new Type[0]).Invoke(new object[0]);
(I have a function to get GetRowType - which is not shown here).
Then to insert into the table I have a separate helper function:
public static void Insert(MyDataContext db, ISession item)
{
// GetTable is defined by Linq2Sql
db.GetTable(GetRowType(domain)).InsertOnSubmit(item);
}

Can I create "relay" or "genericized" properties for Entity Framework models?

I hope my wording makes sense... I wasn't quite sure exactly how to explain what I'm looking to do.
I have a method in a generic class that returns a list of entities as follows:
public abstract class ChildCRUDController<TModel> : CRUDController<TModel, ... >
where TModel : IChildEntity
public ViewResult List(int id)
{
return View(repository.GetMany(x => x.ParentID == id));
}
This controller is implemented by quite a few other controllers. The issue I have is that not all entities that implement IChildEntity have the same parent type. To get around this issue I created ParentID properties for all the models that implement IChildEntity so they could use the same controller.
public partial class PhoneNumber : IChildEntity
{
public int ParentID
{
get { return CustomerID; }
set { CustomerID = ParentID; }
}
}
and...
public partial class Transaction : IChildEntity
{
public int ParentID
{
get { return LeaseID; }
set { LeaseID= ParentID; }
}
}
But when I call the List method above I get the following error:
The specified type member 'ParentID' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
Is there any way I can achieve the result I am looking for without pulling the object set into memory or renaming all the properties on the entities themselves?
Thanks!
If you are willing to pass the field name into the List method and to construct your own query you can do it using the techniques described in this StackOverflow article:
Querying Entity with LINQ using Dyanmic Field Name
Or you could supply the ChildCRUDController with another generic type parameter constrained to an interface that supplies the field name and again use it dynamically.

Possible ways to return a subset of data from Repository<T>?

Let's say I need to display a list of customers, but only want to display the Name and somehow associate the key to the name within a list control.
It would probably be costly to retrieve the entire list of customers and all it's properties. In this scenario, would it be better to create another class with the properties that are required (in this case Id and Name)?
A basic implementation could look like this:
public class Customer {
public int Id { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public int Age { get; set; }
.....
}
public class CustomerListView {
public int Id { get; set; }
public string Name { get; set; }
}
public interface IRepository<T> {
public T Find(int id);
public IEnumerable<T> FindAll();
....
}
public class Repository<T>: IRepository<T> {
....
}
public class CustomerRepository: Repository<Customer> {
public IEnumerable<CustomerListView> FindAllListView();
}
Would this approach be appropriate? What other options would there be?
In order to achieve such goals, I create a simple 'View' class, for example CustomerView, which just contains the properties that are needed to display an overview.
My Repository then has a method which returns a collection of these CustomerView objects.
I mostly use NHibernate in my projects. Nhibernate allows you to use 'projections'.
So, what I do in my repository is this:
(note that the code below is just some pseudo-code; it won't compile).
public IList<CustomerView> GetAllCustomers()
{
ICriteria crit = _session.CreateCriteria (typeof(Customer));
crit.AddProjection ( ... );
crit.SetResultTransformer (new EntityToBeanTransformer(typeof(CustomerView));
return crit.ToList();
}
In fact, it comes down to this: I tell my O/R mapper that it should query Customers, but that it should return entities of type 'CustomerView'.
In the defintion of the projection, I also define which properties of the Customer class map to which properties of the CustomerView class.
Then, the O/R mapper is smart enough to generate a very simple query, which only retrieves those fields that are required to populate the CustomerView class.
For instance, the query that is executed can be as simple as:
SELECT customerid, customername FROM tblCustomer
If you use IQueryable as your return instead of IEnumerable than there is no cost of doing:
CustomerRepository().GetAll().Find(1) because AsQueryable doesn't actually execute until you request data. That means LINQ can optimize it out to a:
SELECT .... FROM .... WHERE ID = 1 instead of
GET EVERYTHING. FIND WHERE THE ID = 1
See this post for an explanation:
Why use AsQueryable() instead of List()?
Using this approach you could create an anonymous class and futher narrow down the data going over the wire to just what you want. That way the query generated by LINQ is optimized to the fullest.
If you have to retrieve the list form a Database then your proposal makes some sense but I would look into a Linq and anonymous type solution.
If the list of Customers already exists in memory then there there are no savings.
You could combine the techniques used by Nissan and Frederik (anonymous types and NHibernate) by using Linq-to-NHibernate.
Item #31 in Bill Wagner's More Effective C# says "limit type scope by using anonymous types", and I agree. BTW, I recommend the whole book.

Extending the String Class With Properties?

I have an application where I need to populate a textbox with a company name and I have filled a custom AutoCompleteStringColection with all the available company names from the database. When a user enters changes the company name by typing and selecting from the list a new company name I need to have the id (Guid), of the selected company so I can do a lookup and get the rest of the company information. Because the company name is not guaranteed to be unique I cannot do a lookup on the name and expect to have the right record. I looked at extending the string class, but all I can find are examples that add methods. I tried that by adding a variable to store the id and methods to get and set the id, but when retrieving the id it is always the last id set. Can a property be added to a class by extending it? I have already changed what I was trying to do to do a lookup on the company name and display a list the user will choose from if multiple matches are returned, but I would still like to know if I can add a property this way in case it comes up again.
No, you cannot extend classes with properties. Additionally, String is sealed so you can't extend it by inheriting. The only recourse is to composition: encapsulate string in your own class.
It sounds like you should create your own class:
class Company {
public string Name {get;set;}
public override string ToString() {return Name;}
// etc
}
Now bind to a set of Company objects; the ToString override will ensure that the Name is displayed by default, and you can add whatever else you need. For more complex scenarios, you can use (for example) DisplayMember and ValueMember (of a combo-box) to point at different properties (rather than the default ToString).
You should use a ComboBox rather than a TextBox. Create a custom type that has your company name and id in it, making sure that it overrides ToString to return the company name. Add those custom types to the ComboBox rather than straight-up strings, and use AutoCompleteSource of ListItems.
I used Konrad's answer and for the sake of completeness I am posting my solution here. I needed to show my user an autocomplete list of company names, but since they could have multiple companies with the same name I needed the Guid id to find their choice in the database. So I wrote my own class inheriting from AutoCompleteStringCollection.
public class AutoCompleteStringWithIdCollection : AutoCompleteStringCollection
{
private List<Guid> _idList = new List<Guid>();
/*-- Properties --*/
public Guid this[int index]
{
get
{
return _idList[index];
}
}
public Guid this[string value]
{
get
{
int index = base.IndexOf(value);
return _idList[index];
}
}
/*-- Methods --*/
public int Add(string value, Guid id)
{
int index = base.Add(value);
_idList.Insert(index, id);
return index;
}
public new void Remove(string value)
{
int index = base.IndexOf(value);
if (index > -1)
{
base.RemoveAt(index);
_idList.RemoveAt(index);
}
}
public new void RemoveAt(int index)
{
base.RemoveAt(index);
_idList.RemoveAt(index);
}
public new void Clear()
{
base.Clear();
_idList.Clear();
}
}

Categories