0

My application has a business logic layer, and a data access layer. I want to give only the data access layer access to the database model. Now, I can easily do this, but then my UI classes cannot access the database classes like Reminder:

namespace Database
{
    using System;
    using System.Collections.Generic;

    public partial class Reminder
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public string Date { get; set; }
        public string RepeatType { get; set; }
        public string Note { get; set; }
        public long Enabled { get; set; }
        public string SoundFilePath { get; set; }
        public string PostponeDate { get; set; }
        public Nullable<long> EveryXCustom { get; set; }
        public string RepeatDays { get; set; }
        public Nullable<long> DayOfMonth { get; set; }
    }
}

which is inside the database class library

enter image description here

I use this reminder class to store reminders. In my UI classes I use this class for various reasons.

To make use of this Reminder class, I simply add a reference to the class library that needs to use it. This works fine, but the problem is that every class library that references this, can alter the database like this.

If I'm not using Entity Framework, I could simply have a Reminder class outside the model (because there is no model) and load reminders from the database into that and extract them without using Entity Framework.

Here's an example of why I need to use the Reminder class in my UI classes (this is just a small code sample of one UI class)

This code is inside a timer that ticks every 30 seconds

// We will check for reminders here every 30 seconds.
foreach (Reminder rem in BLReminder.GetReminders())
{
    // Create the popup. Do the other stuff afterwards.
    if(rem.PostponeDate != null && Convert.ToDateTime(rem.PostponeDate) <= DateTime.Now && rem.Enabled == 1)
    {
        allowRefreshListview = true;

        // temporarily disable it. When the user postpones the reminder, it will be re-enabled.
        rem.Enabled = 0;
        BLReminder.EditReminder(rem);

        MakePopup(rem);
    }
    else if(Convert.ToDateTime(rem.Date.Split(',')[0]) <= DateTime.Now && rem.PostponeDate == null && rem.Enabled == 1)
    {
        allowRefreshListview = true;

        // temporarily disable it. When the user postpones the reminder, it will be re-enabled.
        rem.Enabled = 0;
        BLReminder.EditReminder(rem);

        MakePopup(rem);
    }
}    

GetReminders will do get the reminders from the database and put them in reminder objects

using (RemindMeDbEntities db = new RemindMeDbEntities())
{                
    localReminders = (from g in db.Reminder select g).ToList();
    db.Dispose();                
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
stefan
  • 165
  • 1
  • 15

2 Answers2

1

If im not using the entity framework, i could simply have a reminder class outside the model

You could create an interface instead of a class outside of the model in a shared assembly:

public interface IReminder
{
    public long Id { get; }
    public string Name { get; }
    public string Date { get; }
    public string RepeatType { get; }
    public string Note { get; }
    public long Enabled { get; }
    public string SoundFilePath { get; }
    public string PostponeDate { get; }
    public Nullable<long> EveryXCustom { get; }
    public string RepeatDays { get; }
    public Nullable<long> DayOfMonth { get; }
}

Your Entity can than implement the interface:

public partial class Reminder : IReminder
{
    //...
}

Maybe you want to make your Entities only internal visible and expose public service methods like IEnumerable<IReminder> GetReminders()

Christian Gollhardt
  • 16,510
  • 17
  • 74
  • 111
  • Hmm, i found this very interesting so i went out and tried it, but then i ran into problems. I can't make new instances of an interface, so i cant assign data to it. I also can't call methods like DLReminders.GetReminders() in the business logic layer anymore, because that will give me this errror http://i.imgur.com/bzCi4kq.png, which suggests the solution that im trying to avoid – stefan Jul 31 '17 at 06:41
  • @stefan for the first problem, simple create a class that is derived from `IReminder` and create a instance from it. For the second problem: Don't confuse seperation of concerns with seperation of references ;) If your business layer needs your data layer, then it also needs a reference to it. However, I would go for a DataService that exposes only the interface, The Business layer consumes that data service instead of the raw entities. – Christian Gollhardt Jul 31 '17 at 06:54
  • but, isnt it supposed to be so that the business logic layer can not access the database? Like this image http://www.codeguru.com/images/article/19539/030318_01.gif – stefan Jul 31 '17 at 12:01
1

You can create separate project called i.e. Shared and put there all classes which are used in many projects. Then you need to reference this project by UI project and data access project (and by others which use these classes).

Both will have access to shared classes and UI won't be able to call data access layer directly.

You can also create interface outside of data access layer but if your classes are DTOs (Data Transfer Object) first option will be better.

Roman
  • 11,966
  • 10
  • 38
  • 47
  • > put there all classes which are used in many projects. Which classes? The model's classes? I dont think that's possible – stefan Jul 31 '17 at 06:33
  • @stefan, you can have model classes in another project, see http://nullablecode.com/2013/09/splitting-entity-framework-model-classes-separate-projects/ and https://stackoverflow.com/questions/2464909/generate-poco-classes-in-different-project-to-the-project-with-entity-framework – Roman Jul 31 '17 at 06:51
  • I think this is the best solution, but now im getting weird errors. I followed to guide in your link, and now my entities don't know the classes.. visual studio's code completion does know it though. http://imgur.com/gPxjbJF.gif with the error being cannot convert from 'Database.Entity.Reminder' to 'Reminder' , Database.Entity being the class library with the seperated model classes The Reminder from the Entities is still inside Database(not Database.Entity) from the RemindMeDbModel.Context.cs – stefan Jul 31 '17 at 07:15
  • edit: figured it out. i didnt just have to set a custom tool on the .Context.tt, i also had to remove the model.context.cs from the old project – stefan Jul 31 '17 at 07:39
  • Currently trying to fix the new error "Unable to load the specified metadata resource." ... – stefan Jul 31 '17 at 08:03
  • @stefan, see accepted answer here https://stackoverflow.com/questions/23339335/entity-framework-unable-to-load-the-specified-metadata-resource and here https://stackoverflow.com/questions/689355/metadataexception-unable-to-load-the-specified-metadata-resource – Roman Jul 31 '17 at 08:08
  • I tried that and that didn't work. I edited the first part of the connection string to connectionString="metadata=res://*/ and now im getting "The entity type Songs is not part of the model for the current context." Songs being a model entity like Reminder – stefan Jul 31 '17 at 08:19
  • I finally fixed everything.. phew. I'm really happy with the way it is now. The data access layer can access the database, and the UI classes can not and have to go through the business logic layer. Thanks a lot for pointing this out to me :) ! – stefan Jul 31 '17 at 09:31
  • what an ingeniously smart answer. Thanks @RomanDoskoch and good question OP, this is going to help many people. How cool. Sometimes I wish I was apart of the VS development process, the MSDN articles bore my sometimes.. – MZawg Dec 07 '18 at 16:38
  • @stefan can I ask for a quick clarification here, though. How did you have your data access layer reference its config file for the connection string? It seems VS compiles and points to use the app.config file in the main application. Any ideas? – MZawg Dec 07 '18 at 17:04