We've been using LINQ2SQL on our mvc project for a while and its getting a bit long in the teeth.
One of the issues that we constantly face is the "No supported translation to SQL exists" when we try and do a query using something like.
LINQ2SQL partial class
public class User
{
public bool IsActive
{
get
{
return (StartDate < DateTime.Now || StartDate == null) && (EndDate > DateTime.Now || EndDate == null);
}
}
public DateTime? StartDate{get;set;}
public DateTime? EndDate{get;set;}
}
and the query is something like
Datacontext.Users.Where(u => u.IsActive)
you get
The member 'User.IsActive' has no supported translation to SQL.
We are considering moving to EF code first and I'm curious if this same problem exists, and if not how is EF working differently to get around it?
If you have a user DTO class (that is not partial with the Linq2sql entity) there should be not problem to project into that class.
public class UserDTO
{
public bool IsActive{get;set;}
public DateTime? StartDate{get;set;}
public DateTime? EndDate{get;set;}
}
public IList<UserDTO> GetActiveUsers()
{
using(var db = new DbContext())
{
var users = GetUsers(db);
return users.Where(u => u.IsActive).ToList();
}
}
private IQuerable<UserDTO> GetUsers(DbContext db)
{
return (from u in db.Users
select new UserDTO()
{
StartDate = u.StartDate,
EndDate = u.EndDate,
IsActive = (u.StartDate < DateTime.Now || u.StartDate == null) && (u.EndDate > DateTime.Now || u.EndDate == null)
});
}
Related
In the following case, the method CanUnloadAll checks if there are no overlap in the unloading times, considering all trucks.
The current scenario, should returns TRUE but it is returning FALSE.
What's wrong with the logic in the LINQ query?
using System;
using System.Collections.Generic;
using System.Linq;
public class UnloadingTime
{
public DateTime Start { get; private set; }
public DateTime End { get; private set; }
public UnloadingTime(DateTime start, DateTime end)
{
this.Start = start;
this.End = end;
}
}
public static class UnloadingTrucks
{
public static bool CanUnloadAll(IEnumerable<UnloadingTime> unloadingTimes)
{
return unloadingTimes.Any(
TruckA => unloadingTimes.Any(
TruckB => TruckB != TruckA &&
!((
TruckA.Start.Date >= TruckB.End.Date ||
TruckB.Start.Date >= TruckA.End.Date
))
));
}
public static void Main(string[] args)
{
var format = System.Globalization.CultureInfo.InvariantCulture.DateTimeFormat;
UnloadingTime[] unloadingTimes = new UnloadingTime[]
{
new UnloadingTime(DateTime.Parse("3/4/2019 19:00", format), DateTime.Parse("3/4/2019 20:30", format)),
new UnloadingTime(DateTime.Parse("3/4/2019 22:10", format), DateTime.Parse("3/4/2019 22:30", format)),
new UnloadingTime(DateTime.Parse("3/4/2019 22:40", format), DateTime.Parse("3/4/2019 23:00", format))
};
Console.WriteLine(UnloadingTrucks.CanUnloadAll(unloadingTimes));
}
}
To make it easier, I am using .NET Fiddle.
https://dotnetfiddle.net/Mis663
Regards
Solution:
public static bool CanUnloadAll(IEnumerable<UnloadingTime> unloadingTimes)
{
bool hasOverlap = unloadingTimes.Any(
TruckA => unloadingTimes.Any(
TruckB => TruckB != TruckA &&
!((
TruckA.Start >= TruckB.End ||
TruckB.Start >= TruckA.End
))
));
return !hasOverlap;
}
You are using the DateTime.Date property, which is the date component without the time part. You must use the DateTime:
bool flag = TruckA.Start >= TruckB.End || TruckB.Start >= TruckA.End
I have an object Member,
Member
PropertyA
PropertyZ
Membership[] Memberships
and Membership object is
Membership
PropertyA
PropertyZ
string Status
DateTime? StartDate
DateTime? EndDate
If I now have,
var members = GetAllMembers(DateTime.Now);
I want to find out if member has any active membership today,
// iterating through each member here
if(member.Memberships.Any())
{
// check if user has any membership duration that falls into
// today's date and membership status is also "active"
}
Problem is,
I am not sure how to check if any membership duration that falls into today's date and membership status is also "active"
to check that the targetdt(DateTime.Now) is between StartDate & EndDate:
if (member.Memberships.Any(x=>targetDt.Ticks > x.StartDate.Ticks && targetDt.Ticks < x.EndDate.Ticks && x.Status=="Active"))
{
// Has membership on targetDt.
}
I assume that null value for start or end date means 'open' interval
var today = DateTime.Now;
var activeMembers = allMembers.Where(m =>
m.Memberships.Any(ms =>
ms.Status == "Active" &&
(ms.StartDate == null || ms.StartDate <= today) && (ms.EndDate == null || today <= ms.EndDate)));
So I would structure your main classes slightly more different from what you have now. This it to get of having to compare on strings for the status, but since I don't know exactly how the active property is set but I would assume that for a membership to be active the start and (optional) end date have to be smaller than and greater than today's date.
class Member
{
private readonly IList<Membership> memberships;
public Member()
{
memberships = new List<Membership>();
}
public ReadOnlyCollection<Membership> Memberships
{
get
{
return new ReadOnlyCollection<Membership>(memberships);
}
}
}
class Membership
{
private readonly DateTime endDate;
private readonly DateTime startDate;
public Membership(DateTime startDate)
{
this.startDate = startDate;
}
DateTime StartDate
{
get
{
return this.startDate;
}
}
DateTime? EndDate
{
get
{
return endDate;
}
}
public Status Status
{
get
{
return CalculateMemebershipStatus();
}
}
private Status CalculateMemebershipStatus()
{
var todaysDate = DateTime.Today;
if (StartDate > todaysDate)
{
return Status.Deactivated;
}
if (!EndDate.HasValue)
{
return Status.Active;
}
if (todaysDate < EndDate.Value)
{
return Status.Active;
}
return Status.Deactivated;
}
}
enum Status
{
Active,
Deactivated
}
And when you need to find out all the members that have an active membership today you can do:
var memberCollection = new List<Member>();
...
var activeMembers = memberCollection.Where(x => x.Memberships.Any(y=> y.Status == Status.Active));
So this will go through your member collection and select the ones that have an active status today. Although as previously said this depends on how you build the active property but this is more contained in that testing for the activeness of the membership based on the date is done within the membership object. So the consumer of this data doesn't need to know how to say that a membership is active they simply only need to know that some will be active and others will be deactivated.
For concurrency control i Write "VersionCheck" function in my Context class,I need to dynamically load Context object and check if version is the same as the current context object RowVersion. now i use switch statement. (code below)
and also, Is there more convenient way for version control?
p.s. RowVersion is TimeStamp type in Database.
public class SchoolContext : DbContext
{
public DbSet<Group> Groups { get; set; }
public DbSet<Person> Persons { get; set; }
public bool VersionCheck(string objName)
{
var dbc = new SchoolContext();
byte[] bt1 = null;
byte[] bt2 = null;
switch (objName)
{
case "Person":
dbc.Persons.Load();
bt1 = dbc.Persons.SingleOrDefault(p => p.Id == 1).RowVersion;
bt2 = this.Persons.Local.SingleOrDefault(p => p.Id == 1).RowVersion;
break;
case "Group":
dbc.Groups.Load();
bt1 = dbc.Groups.SingleOrDefault(p => p.Id == 1).RowVersion;
bt2 = this.Groups.Local.SingleOrDefault(p => p.Id == 1).RowVersion;
break;
}
if (bt1 == null && bt2 == null)
{
throw new Exception("One of the Variable is null!");
return true;
}
for (int i = 0; i < bt1.Length; i++)
{
if (bt1[i] != bt2[i])
{
MessageBox.Show("Current object changed!");
return false;
}
}
return true;
}
}
Optimistic concurrency explained
The described approach looks like data corruption waiting to happen.
Unless you are locking the row or table during the time you read and check the rowversion, then it can change after to you have read it to check its value.
Use Optimistic concurrency paradigm properly.
eg https://stackoverflow.com/a/14718991/1347784
I have an ADO.Net Data Access layer in my application that uses basic ADO.Net coupled with CRUD stored procedures (one per operation e.g. Select_myTable, Insert_myTable). As you can imagine, in a large system (like ours), the number of DB objects required by the DA layer is pretty large.
I've been looking at the possibility of refactoring the layer classes into EF POCO classes. I've managed to do this, but when I try to performance test, it gets pretty horrific. Using the class below (create object, set Key to desired value, call dataselect), 100000 runs of data loading only takes about 47 seconds (there are only a handful of records in the DB). Whereas the Stored Proc method takes about 7 seconds.
I'm looking for advice on how to optimise this - as a point of note, I cannot change the exposed functionality of the layer - only how it implements the methods (i.e. I can't pass responsibility for context ownership to the BO layer)
Thanks
public class DAContext : DbContext
{
public DAContext(DbConnection connection, DbTransaction trans)
: base(connection, false)
{
this.Database.UseTransaction(trans);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//Stop Pluralising the Object names for table names.
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
//Set any property ending in "Key" as a key type.
modelBuilder.Properties().Where(prop => prop.Name.ToLower().EndsWith("key")).Configure(config => config.IsKey());
}
public DbSet<MyTable> MyTable{ get; set; }
}
public class MyTable : DataAccessBase
{
#region Properties
public int MyTableKey { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool Active { get; set; }
public int CreatedBy { get; set; }
public DateTime CreatedDate { get; set; }
public int ModifiedBy { get; set; }
public DateTime ModifiedDate { get; set; }
#endregion
#region constructors
public MyTable()
{
//Set Default Values.
Active = true;
Name = string.Empty;
CreatedDate = DateTime.MinValue;
ModifiedDate = DateTime.MinValue;
}
#endregion
#region Methods
public override void DataSelect(System.Data.SqlClient.SqlConnection connection, System.Data.SqlClient.SqlTransaction transaction)
{
using (DAContext ctxt = new DAContext(connection, transaction))
{
var limitquery = from C in ctxt.MyTable
select C;
//TODO: Sort the Query
limitquery = FilterQuery(limitquery);
var limit = limitquery.FirstOrDefault();
if (limit != null)
{
this.Name = limit.Name;
this.Description = limit.Description;
this.Active = limit.Active;
this.CreatedBy = limit.CreatedBy;
this.CreatedDate = limit.CreatedDate;
this.ModifiedBy = limit.ModifiedBy;
this.ModifiedDate = limit.ModifiedDate;
}
else
{
throw new ObjectNotFoundException(string.Format("No MyTable with the specified Key ({0}) exists", this.MyTableKey));
}
}
}
private IQueryable<MyTable1> FilterQuery(IQueryable<MyTable1> limitQuery)
{
if (MyTableKey > 0) limitQuery = limitQuery.Where(C => C.MyTableKey == MyTableKey);
if (!string.IsNullOrEmpty(Name)) limitQuery = limitQuery.Where(C => C.Name == Name);
if (!string.IsNullOrEmpty(Description)) limitQuery = limitQuery.Where(C => C.Description == Description);
if (Active) limitQuery = limitQuery.Where(C => C.Active == true);
if (CreatedBy > 0) limitQuery = limitQuery.Where(C => C.CreatedBy == CreatedBy);
if (ModifiedBy > 0) limitQuery = limitQuery.Where(C => C.ModifiedBy == ModifiedBy);
if (CreatedDate > DateTime.MinValue) limitQuery = limitQuery.Where(C => C.CreatedDate == CreatedDate);
if (ModifiedDate > DateTime.MinValue) limitQuery = limitQuery.Where(C => C.ModifiedDate == ModifiedDate);
return limitQuery;
}
#endregion
}
Selects are slow with tracking on. You should definitely turn off tracking and measure again.
Take a look at my benchmarks
http://netpl.blogspot.com/2013/05/yet-another-orm-micro-benchmark-part-23_15.html
This might be just a hunch, but ... In your stored procedure, the filters are well defined and the SP is in a compiled state with decent execution plan. Your EF query gets constructed from scratch and recompiled on every use. So the task now becomes to devise a way to compile and preserve your EF queries, between uses. One way would be to rewrite your FilterQuery to not rely on fluent conditional method chain. Instead of appending, or not, a new condition every time your parameter set changes, convert it into one, where the filter is either applied when condition is met, or overridden by something like 1.Equals(1) when not. This way your query can be complied and made available for re-use. The backing SQL will look funky, but execution times should improve. Alternatively you could devise Aspect Oriented Programming approach, where compiled queries would be re-used based on parameter values. If I will have the time, I will post a sample on Code Project.
I want to record that last update of user that log in into my website. After they log in success, the laseUpdate filed must change to the current date time.
I have use this code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace ICEWeb.Models
{
public class CustomerModels:ItemEntityDataContext
{
public bool ValidateCustomer(string username, string password)
{
bool b = (
this.DataContext
.Customers
.Where(s => s.ActiveInWebLogin == 1
&&
s.WebAccount == username
&&
s.Password == password
)
.SingleOrDefault()
) != null;
if (b == true)
this.UpdateCustomerLastUpdateStatus();
return b;
}
public void UpdateCustomerLastUpdateStatus()
{
Customer c = new Customer();
c.LastWebLogIn = DateTime.Now;
this.DataContext.SaveChanges();
}
}
}
The validateCustomer() is work but it is not update my record(LastWebLogIn).
Can anyone solve this for me?
Thanks.
You should not create a new instance but ise the current instance of the Customers object and update it to the database. Try something like this:
public bool ValidateCustomer(string username, string password)
{
var user = this.DataContext.Customers
.Where(s => s.ActiveInWebLogin == 1 &&
s.WebAccount == username &&
s.Password == password)
.SingleOrDefault();
if (user != null)
{
this.UpdateCustomerLastUpdateStatus(user);
return true;
}
return false;
}
public void UpdateCustomerLastUpdateStatus(Customers c)
{
c.LastWebLogIn = DateTime.Now;
this.DataContext.SaveChanges();
}
Use Table.InsertOnSubmit Method if you want to create new Customer:
var customer = new Customer();
customer.LastWebLogIn = DateTime.Now;
this.DataContext.Customers.InsertOnSubmit(customer);
this.DataContext.SubmitChanges();
If you want to update existing customer you should do following:
var customer = this.DataContext.Customers
.Where(...)
.Single();
customer.LastWebLogIn = DateTime.Now;
this.DataContext.SaveChanges();
You're setting the LastWebLogIn for an unidentified new customer. Take the customer from the database (as per your first method), and update that object (making sure you haven't optimised with a read-only data-context).
Can you try the following code :
public bool ValidateCustomer(string username, string password)
{
var customer = this.DataContext
.Customers
.Where(s => s.ActiveInWebLogin == 1
&&
s.WebAccount == username
&&
s.Password == password
)
.SingleOrDefault()
if (customer != null)
return this.UpdateCustomerLastUpdateStatus(customer);
return false;
}
public void UpdateCustomerLastUpdateStatus(Customer c)
{
try
{
c.LastWebLogIn = DateTime.Now;
this.DataContext.SaveChanges();
return true;
}
Catch(Exception ex)
{
// Log the error
return false;
}
}